diff --git a/.devcontainer/api/devcontainer.json b/.devcontainer/api/devcontainer.json new file mode 100644 index 0000000000..4002a26b14 --- /dev/null +++ b/.devcontainer/api/devcontainer.json @@ -0,0 +1,51 @@ +{ + "name": "Tracker API", + "dockerComposeFile": "docker-compose.yml", + + // The service in docker-compose that VS Code / Codespaces attaches to + "service": "api", + + // Set the workspace to the api subdirectory of the monorepo. + // docker-compose.yml mounts the repo root at /workspaces, so api/ is at /workspaces/api. + "workspaceFolder": "/workspaces/api", + + "forwardPorts": [4000, 8529, 4222], + "portsAttributes": { + "4000": { + "label": "GraphQL API", + "onAutoForward": "notify" + }, + "8529": { + "label": "ArangoDB Web UI" + }, + "4222": { + "label": "NATS" + } + }, + + // Install dependencies after the container is created. + // Copy .env.example to .env so the API can start without manual setup. + "postCreateCommand": "npm install && [ ! -f .env ] && cp .env.example .env || true", + + // Start the API in watch mode automatically + "postStartCommand": "npm run dev:watch", + + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "apollographql.vscode-apollo", + "lokalise.i18n-ally", + "Orta.vscode-jest" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.workingDirectories": ["."] + } + } + }, + + "remoteUser": "node" +} diff --git a/.devcontainer/api/docker-compose.yml b/.devcontainer/api/docker-compose.yml new file mode 100644 index 0000000000..8889bc5022 --- /dev/null +++ b/.devcontainer/api/docker-compose.yml @@ -0,0 +1,90 @@ +version: "3.8" + +services: + api: + image: mcr.microsoft.com/devcontainers/javascript-node:20 + volumes: + # Mount the monorepo root so all relative paths within the project work + - ../..:/workspaces:cached + command: sleep infinity + environment: + - NODE_ENV=development + - DB_URL=http://arangodb:8529 + - DB_NAME=track_dmarc + - DB_PASS=test + - NATS_URL=nats://nats:4222 + - REDIS_URL=redis://redis:6379 + depends_on: + - arangodb + - nats + - redis + networks: + - tracker-dev + + arangodb: + image: arangodb:3.12.1 + environment: + - ARANGO_ROOT_PASSWORD=test + volumes: + - arangodb-data:/var/lib/arangodb3 + networks: + - tracker-dev + + nats: + image: nats:2.10.16-scratch + command: -js + networks: + - tracker-dev + + nats-init: + image: natsio/nats-box:latest + depends_on: + - nats + volumes: + - ../../k8s/infrastructure/bases/nats/stream-config.json:/stream-config/stream-config.json + entrypoint: > + /bin/sh -c ' + set -e + echo "[nats-stream-init] Waiting for NATS..." + until nc -z nats 4222; do + sleep 1 + done + sleep 2 + + echo "[nats-stream-init] Attempting to add SCANS stream..." + if nats stream add SCANS --config /stream-config/stream-config.json --server nats://nats:4222; then + echo "[nats-stream-init] SCANS stream added successfully." + else + echo "[nats-stream-init] Add failed, attempting to edit SCANS stream..." + if nats stream edit SCANS --config /stream-config/stream-config.json --server nats://nats:4222 --force; then + echo "[nats-stream-init] SCANS stream edited successfully." + else + echo "[nats-stream-init] ERROR: Could not add or edit SCANS stream!" + exit 1 + fi + fi + + echo "[nats-stream-init] Attempting to add WEB_SCANNER_IPS KV bucket..." + if nats kv add "WEB_SCANNER_IPS" --ttl="5m" --server nats://nats:4222; then + echo "[nats-stream-init] WEB_SCANNER_IPS KV bucket added successfully." + else + echo "[nats-stream-init] Add failed, attempting to edit WEB_SCANNER_IPS KV bucket..." + if nats kv edit "WEB_SCANNER_IPS" --ttl="5m" --server nats://nats:4222; then + echo "[nats-stream-init] WEB_SCANNER_IPS KV bucket edited successfully." + else + echo "[nats-stream-init] ERROR: Could not add or edit WEB_SCANNER_IPS KV bucket!" + exit 1 + fi + fi + + echo "[nats-stream-init] Stream initialization complete." + exec sleep infinity + ' + networks: + - tracker-dev + +networks: + tracker-dev: + +volumes: + arangodb-data: diff --git a/.devcontainer/frontend/devcontainer.json b/.devcontainer/frontend/devcontainer.json new file mode 100644 index 0000000000..77ee852119 --- /dev/null +++ b/.devcontainer/frontend/devcontainer.json @@ -0,0 +1,43 @@ +{ + "name": "Tracker Frontend", + "image": "mcr.microsoft.com/devcontainers/javascript-node:20", + + // Set the workspace to the frontend subdirectory of the monorepo + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/frontend", + + "forwardPorts": [3000, 3001], + "portsAttributes": { + "3000": { + "label": "Frontend Dev Server", + "onAutoForward": "openBrowser" + }, + "3001": { + "label": "Webpack Dev Server (internal)" + } + }, + + // Install dependencies after the container is created + "postCreateCommand": "npm install", + + // Start the dev server automatically when the container starts + "postStartCommand": "npm run dev", + + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "dsznajder.es7-react-js-snippets", + "apollographql.vscode-apollo", + "lokalise.i18n-ally" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.workingDirectories": ["./frontend"] + } + } + }, + + "remoteUser": "node" +} diff --git a/.devcontainer/scanners/devcontainer.json b/.devcontainer/scanners/devcontainer.json new file mode 100644 index 0000000000..3367865336 --- /dev/null +++ b/.devcontainer/scanners/devcontainer.json @@ -0,0 +1,46 @@ +{ + "name": "Tracker Scanners", + "dockerComposeFile": "docker-compose.yml", + + // The service in docker-compose that VS Code / Codespaces attaches to + "service": "dev", + + // docker-compose.yml mounts the repo root at /workspaces, so scanners/ is at /workspaces/scanners. + "workspaceFolder": "/workspaces/scanners", + + "forwardPorts": [8529, 8222], + "portsAttributes": { + "8529": { + "label": "ArangoDB Web UI" + }, + "8222": { + "label": "NATS Monitor" + } + }, + + // Install the NATS CLI for injecting test messages, then set up a venv per service. + // + // Each service pins its own Python version. The Microsoft Python devcontainer image + // ships with pyenv, so we install the versions that differ from the container's + // default (3.14) and create isolated venvs per service directory. + // + // python-dotenv will not override env vars already set in docker-compose, so the + // container-friendly host names (arangodb, nats) take precedence over any localhost + // values in local .env files. + "postCreateCommand": "bash /workspaces/.devcontainer/scanners/setup.sh", + + "customizations": { + "vscode": { + "extensions": ["ms-python.python", "ms-python.vscode-pylance", "ms-python.debugpy", "ms-python.black-formatter"], + "settings": { + "editor.formatOnSave": true, + "python.defaultInterpreterPath": "${workspaceFolder}/dns-scanner/.venv/bin/python", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } + } + } + }, + + "remoteUser": "vscode" +} diff --git a/.devcontainer/scanners/docker-compose.yml b/.devcontainer/scanners/docker-compose.yml new file mode 100644 index 0000000000..2b8cd12174 --- /dev/null +++ b/.devcontainer/scanners/docker-compose.yml @@ -0,0 +1,89 @@ +version: "3.8" + +services: + dev: + image: mcr.microsoft.com/devcontainers/python:dev-3.14-bookworm + volumes: + # Mount the monorepo root so all relative paths within the project work + - ../..:/workspaces:cached + command: sleep infinity + environment: + # Override localhost defaults so services connect to named containers + - DB_USER=root + - DB_PASS=test + - DB_NAME=track_dmarc + - DB_URL=http://arangodb:8529 + - NATS_SERVERS=nats://nats:4222 + depends_on: + - arangodb + - nats + networks: + - tracker-dev + + arangodb: + image: arangodb:3.12.1 + environment: + - ARANGO_ROOT_PASSWORD=test + volumes: + - arangodb-data:/var/lib/arangodb3 + networks: + - tracker-dev + + nats: + image: nats:2.10.16-scratch + command: -js + networks: + - tracker-dev + + nats-init: + image: natsio/nats-box:latest + depends_on: + - nats + volumes: + - ../../k8s/infrastructure/bases/nats/stream-config.json:/stream-config/stream-config.json + entrypoint: > + /bin/sh -c ' + set -e + echo "[nats-stream-init] Waiting for NATS..." + until nc -z nats 4222; do + sleep 1 + done + sleep 2 + + echo "[nats-stream-init] Attempting to add SCANS stream..." + if nats stream add SCANS --config /stream-config/stream-config.json --server nats://nats:4222; then + echo "[nats-stream-init] SCANS stream added successfully." + else + echo "[nats-stream-init] Add failed, attempting to edit SCANS stream..." + if nats stream edit SCANS --config /stream-config/stream-config.json --server nats://nats:4222 --force; then + echo "[nats-stream-init] SCANS stream edited successfully." + else + echo "[nats-stream-init] ERROR: Could not add or edit SCANS stream!" + exit 1 + fi + fi + + echo "[nats-stream-init] Attempting to add WEB_SCANNER_IPS KV bucket..." + if nats kv add "WEB_SCANNER_IPS" --ttl="5m" --server nats://nats:4222; then + echo "[nats-stream-init] WEB_SCANNER_IPS KV bucket added successfully." + else + echo "[nats-stream-init] Add failed, attempting to edit WEB_SCANNER_IPS KV bucket..." + if nats kv edit "WEB_SCANNER_IPS" --ttl="5m" --server nats://nats:4222; then + echo "[nats-stream-init] WEB_SCANNER_IPS KV bucket edited successfully." + else + echo "[nats-stream-init] ERROR: Could not add or edit WEB_SCANNER_IPS KV bucket!" + exit 1 + fi + fi + + echo "[nats-stream-init] Stream initialization complete." + exec sleep infinity + ' + networks: + - tracker-dev + +networks: + tracker-dev: + +volumes: + arangodb-data: diff --git a/.devcontainer/scanners/setup.sh b/.devcontainer/scanners/setup.sh new file mode 100644 index 0000000000..4bb1ecfa16 --- /dev/null +++ b/.devcontainer/scanners/setup.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# The script lives at .devcontainer/scanners/setup.sh — two dirs up is the repo root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SCANNERS_DIR="$REPO_ROOT/scanners" + +# --------------------------------------------------------------------------- +# NATS CLI — useful for publishing test messages and inspecting streams +# --------------------------------------------------------------------------- +echo ">>> Installing NATS CLI..." +NATS_VERSION="0.2.2" +ARCH="$(dpkg --print-architecture)" # amd64 or arm64 +curl -fsSL "https://github.com/nats-io/natscli/releases/download/v${NATS_VERSION}/nats-${NATS_VERSION}-linux-${ARCH}.zip" \ + -o /tmp/nats.zip +unzip -q /tmp/nats.zip -d /tmp/nats +sudo mv /tmp/nats/nats-*/nats /usr/local/bin/nats +rm -rf /tmp/nats /tmp/nats.zip +echo " nats $(nats --version)" + +# --------------------------------------------------------------------------- +# pyenv — install if not already present, then bootstrap for this session. +# Each scanner service pins its own Python version in its Dockerfile; we +# use pyenv to create isolated venvs per service without touching system Python. +# --------------------------------------------------------------------------- +export PYENV_ROOT="$HOME/.pyenv" +if [ ! -x "$PYENV_ROOT/bin/pyenv" ]; then + echo ">>> Installing build dependencies..." + # Required to compile CPython (pyenv) and native Python extensions (e.g. pydantic-core) + sudo apt-get update -qq + sudo apt-get install -y -qq \ + build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \ + libsqlite3-dev libffi-dev liblzma-dev libncurses-dev + + echo ">>> Installing Rust (required for pydantic-core and other native extensions)..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path + # Source immediately so cargo is available for the rest of this block + source "$HOME/.cargo/env" + + echo ">>> Installing pyenv..." + curl -fsSL https://pyenv.run | bash + + echo ">>> Installing rust-query-crlite (used by web-scanner for TLS revocation checks)..." + cargo install \ + --git https://github.com/mozilla/crlite rust-query-crlite \ + --tag v1.0.39 +fi + +# Make Rust and pyenv available for this session +export PATH="$HOME/.cargo/bin:$PATH" +export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init --path)" + +# Map service → Python version (sourced from each service's Dockerfile) +declare -A SERVICE_PYTHON=( + [dns-scanner]="3.13.5" + [dns-processor]="3.14.2" + [web-scanner]="3.12.8" + [web-processor]="3.14.2" +) + +for service in "${!SERVICE_PYTHON[@]}"; do + version="${SERVICE_PYTHON[$service]}" + svc_dir="$SCANNERS_DIR/$service" + + echo ">>> [$service] Setting up Python $version venv..." + + # Install the version if pyenv doesn't have it yet. + # --enable-shared is required for maturin/pyo3 (e.g. pydantic-core) to link + # against libpython. Without it, maturin ignores the venv and falls back to + # the system Python, causing version mismatches and build failures. + if ! pyenv versions --bare | grep -qx "$version"; then + PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install "$version" + fi + + # Create venv using the pinned version + "$PYENV_ROOT/versions/$version/bin/python" -m venv "$svc_dir/.venv" + + # Install dependencies + "$svc_dir/.venv/bin/pip" install --quiet --upgrade pip + "$svc_dir/.venv/bin/pip" install --quiet -r "$svc_dir/requirements.txt" + + # Copy .env.example → .env if no .env exists yet + if [ ! -f "$svc_dir/.env" ] && [ -f "$svc_dir/.env.example" ]; then + cp "$svc_dir/.env.example" "$svc_dir/.env" + echo " Copied .env.example → .env (fill in secrets before running)" + fi + + echo " Done." +done + +echo "" +echo "Setup complete. Tips:" +echo " • cd into a service directory and activate its venv: source .venv/bin/activate" +echo " • Run the service: python service.py" +echo " • Publish a test scan request via NATS CLI:" +echo " nats pub --server nats://nats:4222 scans.requests '{\"domain\": \"example.com\"}'" +echo " • Inspect the SCANS stream:" +echo " nats stream info SCANS --server nats://nats:4222" diff --git a/.editorconfig b/.editorconfig index ee599f9a60..3dd624d30a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true -max_line_length = 80 +max_line_length = 120 trim_trailing_whitespace = true [*.md] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a38107c721..c90437abd7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,190 +1,393 @@ version: 2 + updates: -- package-ecosystem: docker - directory: "/api-js" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: npm - directory: "/api-js" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/ci" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/clients/python" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/frontend" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: npm - directory: "/frontend" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/auto-scan" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/auto-scan" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/core" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/core" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/dmarc-report" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: npm - directory: "/services/dmarc-report" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/result-queue" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/result-queue" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/scanners/dns" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/scanners/dns" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/scanners/https" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/scanners/https" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/results" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/results" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/scanners/ssl" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/scanners/ssl" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/scan-queue" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: pip - directory: "/services/scan-queue" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: docker - directory: "/services/super-admin" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: npm - directory: "/services/super-admin" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 - -- package-ecosystem: npm - directory: "/scripts/insert-json" - schedule: - interval: daily - time: "11:00" - open-pull-requests-limit: 5 + # Monday — frontend (standalone, largest dep file) + - package-ecosystem: docker + directory: "/frontend" + schedule: + interval: weekly + day: monday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/frontend" + schedule: + interval: weekly + day: monday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + # Tuesday — api (standalone, large dep file) + - package-ecosystem: docker + directory: "/api" + schedule: + interval: weekly + day: tuesday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/api" + schedule: + interval: weekly + day: tuesday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + # Wednesday — dmarc-report + super-admin (medium services) + - package-ecosystem: docker + directory: "/services/dmarc-report" + schedule: + interval: weekly + day: wednesday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/services/dmarc-report" + schedule: + interval: weekly + day: wednesday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/services/super-admin" + schedule: + interval: weekly + day: wednesday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/services/super-admin" + schedule: + interval: weekly + day: wednesday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + # Thursday — domain-cleanup + org-footprint (medium services) + - package-ecosystem: docker + directory: "/services/domain-cleanup" + schedule: + interval: weekly + day: thursday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/services/domain-cleanup" + schedule: + interval: weekly + day: thursday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/services/org-footprint" + schedule: + interval: weekly + day: thursday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/services/org-footprint" + schedule: + interval: weekly + day: thursday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + # Friday — progress-report + domain-dispatcher (medium services) + - package-ecosystem: docker + directory: "/services/progress-report" + schedule: + interval: weekly + day: friday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/services/progress-report" + schedule: + interval: weekly + day: friday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/scanners/domain-dispatcher" + schedule: + interval: weekly + day: friday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: npm + directory: "/scanners/domain-dispatcher" + schedule: + interval: weekly + day: friday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + # Saturday — web-scanner + dns-scanner + domain-discovery (small scanners) + - package-ecosystem: docker + directory: "/scanners/web-scanner" + schedule: + interval: weekly + day: saturday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/scanners/web-scanner" + schedule: + interval: weekly + day: saturday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/scanners/dns-scanner" + schedule: + interval: weekly + day: saturday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/scanners/dns-scanner" + schedule: + interval: weekly + day: saturday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/scanners/domain-discovery" + schedule: + interval: weekly + day: saturday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/scanners/domain-discovery" + schedule: + interval: weekly + day: saturday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + # Sunday — update-selectors + summaries + azure-defender-easm (smallest services) + - package-ecosystem: docker + directory: "/services/update-selectors" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/services/update-selectors" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/services/summaries" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/services/summaries" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/azure-defender-easm/import-easm-additional-findings" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/azure-defender-easm/import-easm-additional-findings" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/azure-defender-easm/label-known-easm-assets" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/azure-defender-easm/label-known-easm-assets" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: docker + directory: "/azure-defender-easm/add-domain-to-easm" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 + + - package-ecosystem: pip + directory: "/azure-defender-easm/add-domain-to-easm" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + semver-minor-days: 14 + semver-major-days: 30 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 916f5fcd64..b3d26e2bcc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,50 +1,50 @@ -name: "CodeQL" +name: CodeQL - Analysis on: + push: + branches: ["master"] + pull_request: + branches: ["master"] schedule: - # Run every Tuesday + # Run every Tuesday at 00:00 UTC - cron: "0 0 * * 2" +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +permissions: + security-events: write + actions: read + contents: read + jobs: analyze: - name: Analyze + name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest + timeout-minutes: 60 strategy: fail-fast: false matrix: - language: [ 'javascript', 'python' ] + language: + - javascript-typescript + - python + - actions steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 - + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + # Built-in query suites: security-extended includes more rules than + # the default suite and is recommended for security-focused scanning. + queries: security-extended + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/dependabot-gcbrun.yml b/.github/workflows/dependabot-gcbrun.yml new file mode 100644 index 0000000000..f35c2b6bd8 --- /dev/null +++ b/.github/workflows/dependabot-gcbrun.yml @@ -0,0 +1,21 @@ +name: Trigger Cloud Build for Dependabot +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + gcbrun: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Post /gcbrun as collaborator + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GCB_TRIGGER_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '/gcbrun' + }) diff --git a/.github/workflows/set-maintenance.yml b/.github/workflows/set-maintenance.yml new file mode 100644 index 0000000000..fb86a1b0e9 --- /dev/null +++ b/.github/workflows/set-maintenance.yml @@ -0,0 +1,50 @@ +name: Set/Remove Maintenance Page + +on: + workflow_dispatch: + inputs: + deployment: + # Which deployment to adjust maintenance page + description: 'Which deployment to adjust' + # Input has to be provided for the workflow to run + required: true + # The data type of the input + type: choice + options: + - 'staging' + - 'production' + - 'gke' + + onOff: + # Turning the maintenance page on or off + description: 'Turn the maintenance page on or off' + # Input has to be provided for the workflow to run + required: true + # The data type of the input + type: choice + options: + - 'on' + - 'off' + +jobs: + update_value: + permissions: + contents: write + # The type of runner that the job will run on + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.TRACKER_SUIVI_WRITE_REPO_PAT }} + + - name: Update maintenance value + run: | + if [ "${{ inputs.onOff }}" == "on" ]; then PREFIX=""; else PREFIX="#"; fi + sed -i "s/\s*#\{0,1\}\s*\(- \.\.\/\.\.\/bases\/frontend\/maintenance-override-component\)/${PREFIX} \1/g" k8s/apps/overlays/${{ inputs.deployment }}/kustomization.yaml + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Set maintenance page ${{ inputs.onOff }} for ${{ inputs.deployment }} + commit_author: ${{ github.triggering_actor }} <${{ github.triggering_actor }}@users.noreply.github.com> + file_pattern: 'k8s/apps/overlays/${{ inputs.deployment }}/kustomization.yaml' diff --git a/.gitignore b/.gitignore index 50c02289c7..f329dc33a4 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ **/.DS_Store -platform/**/*.json frontend/public api/schema.json api/api.current.graphql @@ -22,6 +21,8 @@ api/api.current.graphql **/*.env **/.venv venv +**/lib64 +**/pyvenv.cfg # Linting **/.mypy_cache/ @@ -43,6 +44,7 @@ scanner/logs # IDE Files .idea/ .vscode/ +*.code-workspace # all dist directories **/dist diff --git a/Makefile b/Makefile index be41df3b96..d45e26e47f 100644 --- a/Makefile +++ b/Makefile @@ -7,26 +7,34 @@ name = test region = northamerica-northeast1 mode = dev env = test -displayname="admin" -SA_USER_USERNAME="admin@example.com"" -SA_USER_PASSWORD="admin" +displayname=admin +SA_USER_USERNAME=admin@example.com +SA_USER_PASSWORD=admin define scanners = endef .PHONY: cluster cluster: - gcloud beta container --project "$(project)" clusters create "$(name)" --region "$(region)" --no-enable-basic-auth --cluster-version "1.21.1-gke.400" --release-channel "rapid" --machine-type "n2d-standard-4" --image-type "COS_CONTAINERD" --disk-type "pd-standard" --disk-size "50" --metadata disable-legacy-endpoints=true --service-account "gke-node-service-account@track-compliance.iam.gserviceaccount.com" --num-nodes "2" --enable-stackdriver-kubernetes --enable-ip-alias --network "projects/track-compliance/global/networks/default" --subnetwork "projects/track-compliance/regions/northamerica-northeast1/subnetworks/default" --no-enable-master-authorized-networks --addons HorizontalPodAutoscaling,HttpLoadBalancing,CloudRun --enable-autoupgrade --enable-autorepair --max-surge-upgrade 1 --max-unavailable-upgrade 0 --workload-pool "track-compliance.svc.id.goog" --enable-shielded-nodes --shielded-secure-boot --enable-dataplane-v2 + gcloud container --project "$(project)" clusters create "$(name)" --region "$(region)" --no-enable-basic-auth --release-channel "stable" --cluster-version "1.24.11-gke.1000" --machine-type "n2d-standard-4" --image-type "COS_CONTAINERD" --disk-type "pd-standard" --disk-size "50" --metadata disable-legacy-endpoints=true --service-account "gke-least-priviledge-account@$(project).iam.gserviceaccount.com" --num-nodes "2" --logging=SYSTEM,WORKLOAD --monitoring=SYSTEM --enable-ip-alias --network "projects/$(project)/global/networks/default" --subnetwork "projects/$(project)/regions/northamerica-northeast1/subnetworks/default" --no-enable-master-authorized-networks --addons HorizontalPodAutoscaling,HttpLoadBalancing --enable-autoupgrade --enable-autorepair --max-surge-upgrade 1 --max-unavailable-upgrade 0 --workload-pool "$(project).svc.id.goog" --enable-shielded-nodes --enable-dataplane-v2 --shielded-secure-boot --shielded-integrity-monitoring + +.PHONY: gke-service-account +gke-service-account: + gcloud iam service-accounts create gke-least-priviledge-account --display-name="gke least priviledge account" + gcloud projects add-iam-policy-binding php-observatory --member "serviceAccount:gke-least-priviledge-account@php-observatory.iam.gserviceaccount.com" --role roles/logging.logWriter + gcloud projects add-iam-policy-binding php-observatory --member "serviceAccount:gke-least-priviledge-account@php-observatory.iam.gserviceaccount.com" --role roles/monitoring.metricWriter + gcloud projects add-iam-policy-binding php-observatory --member "serviceAccount:gke-least-priviledge-account@php-observatory.iam.gserviceaccount.com" --role roles/monitoring.viewer + gcloud projects add-iam-policy-binding php-observatory --member "serviceAccount:gke-least-priviledge-account@php-observatory.iam.gserviceaccount.com" --role roles/stackdriver.resourceMetadata.writer + .PHONY: secrets secrets: -ifeq ("$(env)", "gke") - kustomize build platform/creds/prod | kubectl apply -f - - kustomize build app/creds/prod | kubectl apply -f - -else - kustomize build platform/creds/dev/ | kubectl apply -f - - kustomize build app/creds/dev | kubectl apply -f - -endif + kustomize build k8s/apps/bases/api/creds | kubectl apply -f - + kustomize build k8s/apps/bases/scanners/dmarc-report-cronjob/creds | kubectl apply -f - + kustomize build k8s/apps/bases/scanners/scanner-platform/creds | kubectl apply -f - + kustomize build k8s/apps/bases/super-admin/creds | kubectl apply -f - + kustomize build k8s/apps/bases/arangodb/creds | kubectl apply -f - + kustomize build k8s/clusters/auto-image-update/bases/creds | kubectl apply -f - .PHONY: dbdump dbdump: @@ -36,134 +44,155 @@ dbdump: restore: arangorestore --server.database track_dmarc --create-collection false --include-system-collections true --input-directory $(from) +.PHONY: install-arango-operator +install-arango-operator: + kubectl apply -f $(arango_operator_manifest_url_prefix)/arango-crd.yaml + kubectl apply -f $(arango_operator_manifest_url_prefix)/arango-deployment.yaml + .PHONY: update-flux update-flux: - flux install --components=source-controller,kustomize-controller,notification-controller,image-reflector-controller,image-automation-controller --export > deploy/bases/flux.yaml + flux install --components=source-controller,kustomize-controller,notification-controller,image-reflector-controller,image-automation-controller --export > k8s/clusters/platform/crds.yaml .PHONY: update-istio update-istio: - istioctl operator dump > platform/components/istio/istio.yaml + istioctl manifest generate -f k8s/infrastructure/bases/istio/istio-operator.yaml --dry-run > k8s/infrastructure/bases/istio/platform/crds.yaml .PHONY: print-arango-deployment print-arango-deployment: - kustomize build app/$(env) | yq -y '. | select(.kind == "ArangoDeployment" and .metadata.name == "arangodb")' + kustomize build k8s/infrastructure/bases/arangodb | yq -y '. | select(.kind == "ArangoDeployment" and .metadata.name == "arangodb")' + +.PHONY: print-istio-operator +print-istio-operator: + kustomize build k8s/infrastructure/overlays/$(env) | yq -y '. | select(.kind == "IstioOperator" and .metadata.name == "istio-operator")' .PHONY: platform platform: - kustomize build platform/$(env) | kubectl apply -f - + kustomize build k8s/infrastructure/overlays/$(env) | kubectl apply -f - .PHONY: deploy deploy: ifeq ("$(env)", "gke") - kustomize build deploy/creds/readwrite | kubectl apply -f - - kustomize build deploy/$(env) | kubectl apply -f - + kustomize build k8s/clusters/auto-image-update/bases/creds | kubectl apply -f - + kustomize build k8s/infrastructure/overlays/$(env) | kubectl apply -f - + kustomize build k8s/apps/overlays/$(env) | kubectl apply -f - else - kustomize build deploy/$(env) | kubectl apply -f - + kustomize build k8s/infrastructure/overlays/$(env) | kubectl apply -f - + kustomize build k8s/apps/overlays/$(env) | kubectl apply -f - endif .PHONY: app app: - kustomize build app/$(env) | kubectl apply -f - + kubectl apply -k k8s/overlays/$(env) + +.PHONY: scan +scan: + kubectl delete job domain-dispatcher-manual -n scanners --ignore-not-found && + kubectl create job domain-dispatcher-manual --from=cronjob/domain-dispatcher -n scanners -.PHONY: scans -scans: - kubectl apply -n scanners -f app/jobs/scan-job.yaml - kubectl apply -n scanners -f app/jobs/core-job.yaml +.PHONY: detect-decay +detect-decay: + kubectl delete job detect-decay-manual -n scanners --ignore-not-found && + kubectl create job detect-decay-manual --from=cronjob/detect-decay -n scanners + +.PHONY: scanners +scanners: + kustomize build scanners | kubectl apply -f - .PHONY: backup backup: - kustomize build app/jobs/backup/$(env) | kubectl apply -f - + kubectl delete job arangodb-backup-manual -n db --ignore-not-found && + kubectl create job arangodb-backup-manual -n db --from=cronjob/backup .PHONY: superadmin superadmin: - kubectl apply -f app/jobs/super-admin.yaml + kubectl apply -k k8s/jobs/super-admin + +.PHONY: guidance +guidance: + kubectl apply -f services/guidance/guidance-job.yaml + +.PHONY: summaries +summaries: + kubectl delete job summaries-manual -n scanners --ignore-not-found && + kubectl create job summaries-manual --from=cronjob/summaries -n scanners + +.PHONY: domain-cleanup +domain-cleanup: + kubectl delete job domain-cleanup-manual -n api --ignore-not-found && + kubectl create job domain-cleanup-manual --from=cronjob/domain-cleanup -n api + +.PHONY: reports +reports: + kubectl delete job dmarc-report-manual -n scanners --ignore-not-found && + kubectl create job dmarc-report-manual --from=cronjob/dmarc-report -n scanners .ONESHELL: .PHONY: credentials credentials: - @cat <<-'EOF' > app/creds/$(mode)/scanners.env + @cat <<-'EOF' > k8s/apps/bases/scanners/scanner-platform/creds/scanners.env DB_PASS=test - DB_HOST=arangodb.db + DB_URL=http://arangodb.db:8529 DB_USER=root DB_NAME=track_dmarc - GITHUB_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - REDIS_HOST=redis-service.scanners - REDIS_PORT=6379 EOF - cat <<-'EOF' > platform/creds/$(mode)/kiali.env - username=admin - passphrase=admin - EOF - cat <<-'EOF' > app/creds/$(mode)/arangodb.env + cat <<-'EOF' > k8s/infrastructure/bases/arangodb/creds/arangodb.env username=root password=test EOF - cat <<-'EOF' > app/creds/$(mode)/dmarc.env + cat <<-'EOF' > k8s/apps/bases/scanners/dmarc-report-cronjob/creds/dmarc.env DB_PASS=dbpass DB_URL=http://arangodb.db:8529/ DB_NAME=track_dmarc - GITHUB_BRANCH=master - GITHUB_FILE=dmarc-domains.json - GITHUB_OWNER=cybercentrecanada - GITHUB_REPO=dmarc-tbs-domains + GITHUB_BRANCH=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + GITHUB_FILE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + GITHUB_OWNER=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + GITHUB_REPO=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx GITHUB_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx GITHUB_URL=https://api.github.com/graphql AZURE_CONN_STRING=??? DATABASE=tbs-tracker SUMMARIES_CONTAINER=tbs-tracker-summaries EOF - cat <<-'EOF' > app/creds/$(mode)/api.env - DB_PASS=test - DB_URL=http://arangodb.db:8529 - DB_NAME=track_dmarc + cat <<-'EOF' > k8s/apps/bases/api/creds/api.env AUTHENTICATED_KEY=alonghash - REFRESH_KEY=alonghash - SIGN_IN_KEY=alonghash AUTH_TOKEN_EXPIRY=60 - REFRESH_TOKEN_EXPIRY=7 CIPHER_KEY=1234averyveryveryveryverylongkey - NOTIFICATION_API_KEY=test_key-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca - DMARC_REPORT_API_SECRET=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - TOKEN_HASH=somelonghash - DMARC_REPORT_API_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - DMARC_REPORT_API_URL=http://localhost:4001/graphql - DEPTH_LIMIT=15 COST_LIMIT=75000 - SCALAR_COST=1 - OBJECT_COST=1 + DB_NAME=track_dmarc + DB_PASS=test + DB_URL=http://arangodb.db:8529 + DEPTH_LIMIT=15 + HASHING_SALT=somerandomvalue LIST_FACTOR=1 - DNS_SCANNER_ENDPOINT=http://ots-scan-queue.scanners.svc.cluster.local/dns - HTTPS_SCANNER_ENDPOINT=http://ots-scan-queue.scanners.svc.cluster.local/https - SSL_SCANNER_ENDPOINT=http://ots-scan-queue.scanners.svc.cluster.local/ssl + LOGIN_REQUIRED=true + NATS_URL=nats://nats:4222 + NOTIFICATION_API_KEY=test_key-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca NOTIFICATION_AUTHENTICATE_EMAIL_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX NOTIFICATION_AUTHENTICATE_TEXT_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - NOTIFICATION_ORG_INVITE_EN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - NOTIFICATION_ORG_INVITE_FR=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - NOTIFICATION_PASSWORD_RESET_EN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - NOTIFICATION_PASSWORD_RESET_FR=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + NOTIFICATION_ORG_INVITE_BILINGUAL=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + NOTIFICATION_PASSWORD_RESET_BILINGUAL=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX NOTIFICATION_TWO_FACTOR_CODE_EN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX NOTIFICATION_TWO_FACTOR_CODE_FR=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX NOTIFICATION_VERIFICATION_EMAIL_EN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX NOTIFICATION_VERIFICATION_EMAIL_FR=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - REDIS_PORT_NUMBER=6379 - REDIS_DOMAIN_NAME=redis-service.scanners - DKIM_SCAN_CHANNEL=scan/dkim - DMARC_SCAN_CHANNEL=scan/dmarc - HTTPS_SCAN_CHANNEL=scan/https - SPF_SCAN_CHANNEL=scan/spf - SSL_SCAN_CHANNEL=scan/ssl + OBJECT_COST=1 + REFRESH_KEY=alonghash + REFRESH_TOKEN_EXPIRY=7 + SCALAR_COST=1 + SERVICE_ACCOUNT_EMAIL=xxxxxx@xxxx + SIGN_IN_KEY=alonghash TRACING_ENABLED=false EOF - cat <<-'EOF' > app/creds/$(mode)/superadmin.env + cat <<-'EOF' > k8s/jobs/super-admin/creds/super-admin.env DB_PASS=test DB_URL=arangodb.db:8529 DB_NAME=track_dmarc - SA_USER_DISPLAY_NAME="$(displayname)" - SA_USER_USERNAME="$(username)" - SA_USER_PASSWORD="$(password)" + SA_USER_DISPLAY_NAME=$(displayname) + SA_USER_USERNAME=$(username) + SA_USER_PASSWORD=$(password) SA_USER_LANG=en SA_ORG_EN_SLUG=sa SA_ORG_EN_ACRONYM=SA @@ -182,4 +211,4 @@ credentials: SA_ORG_FR_PROVINCE=Ontario SA_ORG_FR_CITY=Ottawa EOF - echo "Credentials written to app/creds/$(mode)" + echo "Credentials written" diff --git a/README.md b/README.md index 01a9378aa3..729dd1a9d0 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,13 @@ As is common with microservices projects, this repository is organized in the [m ``` . -├── api-js -├── app +├── api ├── ci ├── clients ├── CONTRIBUTING.md -├── deploy ├── frontend ├── guidance ├── Makefile -├── platform ├── README.md ├── scripts ├── SECURITY.md @@ -33,9 +30,9 @@ As is common with microservices projects, this repository is organized in the [m The [ci](ci/README.md) folder contains an image used in the CI process, but the main event is the next three folders: -The [frontend](frontend/README.md) and [api-js](api-js/README.md) folders contain the two main parts parts of the application. +The [frontend](frontend/README.md) and [api](api/README.md) folders contain the two main parts of the application. -The [app](app/README.md), [platform](platform/README.md) and [deploy](deploy/README.md) folders contain the Kubernetes configuration needed to continuously deploy the tracker on the cloud provider of your choice. +The [k8s](k8s/README.md) folder contain the Kubernetes configurations needed to continuously deploy the tracker on the cloud provider of your choice. The clients folder contains API clients offered as an alternative to Tracker's web frontend. Only a [Python client](clients/python/README.md) is available at this time. @@ -43,11 +40,9 @@ The services folder contains smaller services dedicated to scanning or account c The scripts folder is a dumping ground for various utility scripts and codemods. - ## Running it locally -Running Tracker locally takes a few commands and a lot of RAM. See the instructions in the [app folder](app/README.md) - +Running Tracker locally takes a few commands and a lot of RAM. See the instructions in the [k8s folder](k8s/README.md) ## Deploying to the cloud @@ -73,25 +68,23 @@ $ make platform env= $ make app env= ``` -Tracker is now deployed. To add coninuous deployment functionality via [Flux](https://fluxcd.io/) (this will ensure the Tracker deployment stays up to date with all the latest changes), follow the instructions listed below. - +Tracker is now deployed. To add continuous deployment functionality via [Flux](https://fluxcd.io/) (this will ensure the Tracker deployment stays up to date with all the latest changes), follow the instructions listed below. ### NOTE: Steps 1) and 2) are only required if the Tracker deployment should write back to this repository, updating image tags as necessary. - -1) Create SSH key: +1. Create SSH key: ``` -ssh-keygen -q -N "" -C "flux-read-write" -f ./deploy/creds/readwrite/identity +ssh-keygen -q -N "" -C "flux-read-write" -f ./k8s/clusters/auto-image-update/bases/creds/identity ``` ``` -ssh-keygen github.com > ./deploy/creds/readwrite/known_hosts +ssh-keygen github.com > ./k8s/clusters/auto-image-update/bases/creds/known_hosts ``` -2) [Add key to repository](https://github.com/canada-ca/tracker/settings/keys/new) +2. [Add key to repository](https://github.com/canada-ca/tracker/settings/keys/new) -3) Finally, run: +3. Finally, run: ``` $ make deploy env= diff --git a/SECURITY.md b/SECURITY.md index 8d9b3e1121..985c1f8125 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,11 +1,14 @@ ([Français](#sécurité)) -# Security +# Reporting Security Issues -**Do not post any security issues on the public repository!** Security vulnerabilities must be reported by email to zzcybers@tbs-sct.gc.ca +To report a security issue, email [zztbscybers@tbs-sct.gc.ca](mailto:zztbscybers@tbs-sct.gc.ca) and include the word "SECURITY" in the subject line. +The TBS team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. ______________________ -## Sécurité +## Signalement des problèmes de sécurité -**Ne publiez aucun problème de sécurité sur le dépôt publique!** Les vulnérabilités de sécurité doivent être signalées par courriel à zzcybers@tbs-sct.gc.ca \ No newline at end of file +Pour signaler un problème de sécurité, envoyez un courriel à [zztbscybers@tbs-sct.gc.ca](mailto:zztbscybers@tbs-sct.gc.ca) et ajoutez le mot « SÉCURITÉ » à la ligne d’objet. + +L’équipe du SCT enverra une réponse indiquant les prochaines étapes de la gestion de votre rapport. Après la réponse initiale à votre rapport, l’équipe de sécurité vous tiendra au courant des progrès vers une solution et l’annonce complète, et peut demander des informations ou des conseils supplémentaires. diff --git a/api-js/.env.example b/api-js/.env.example deleted file mode 100644 index ac1f8ac9d1..0000000000 --- a/api-js/.env.example +++ /dev/null @@ -1,42 +0,0 @@ -DB_PASS= -DB_URL= -DB_NAME= -AUTHENTICATED_KEY= -AUTH_TOKEN_EXPIRY= -REFRESH_TOKEN_EXPIRY= -SIGN_IN_KEY= -REFRESH_KEY= -NOTIFICATION_API_URL= -NOTIFICATION_API_KEY= -NOTIFICATION_AUTHENTICATE_EMAIL_ID= -NOTIFICATION_AUTHENTICATE_TEXT_ID= -NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN= -NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR= -NOTIFICATION_ORG_INVITE_EN= -NOTIFICATION_ORG_INVITE_FR= -NOTIFICATION_PASSWORD_RESET_EN= -NOTIFICATION_PASSWORD_RESET_FR= -NOTIFICATION_TWO_FACTOR_CODE_EN= -NOTIFICATION_TWO_FACTOR_CODE_FR= -NOTIFICATION_VERIFICATION_EMAIL_EN= -NOTIFICATION_VERIFICATION_EMAIL_FR= -DMARC_REPORT_API_URL= -DMARC_REPORT_API_TOKEN= -DMARC_REPORT_API_SECRET= -DEPTH_LIMIT= -COST_LIMIT= -SCALAR_COST= -OBJECT_COST= -LIST_FACTOR= -CIPHER_KEY= -DNS_SCANNER_ENDPOINT= -HTTPS_SCANNER_ENDPOINT= -SSL_SCANNER_ENDPOINT= -TRACING_ENABLED= -REDIS_PORT_NUMBER= -REDIS_DOMAIN_NAME= -DKIM_SCAN_CHANNEL= -DMARC_SCAN_CHANNEL= -HTTPS_SCAN_CHANNEL= -SPF_SCAN_CHANNEL= -SSL_SCAN_CHANNEL= diff --git a/api-js/Dockerfile b/api-js/Dockerfile deleted file mode 100644 index 8ea16afa3f..0000000000 --- a/api-js/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# To build image: docker build --tag tracker-api:1.0 . -# To run image: docker run --network=host --env-file ./.env tracker-api:1.0 -# Build image -FROM node:alpine AS base - -WORKDIR /app - -FROM base AS builder - -COPY package*.json .babelrc ./ - -RUN npm install - -COPY ./src ./src -COPY ./index.js . -COPY ./database-options.js . -COPY ./.env.example . - -RUN npm run build - -RUN npm prune --production - -# Prod image -FROM base AS release - -ENV NODE_ENV production - -COPY --from=builder /app/package.json . -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.env.example . -COPY --from=builder /app/index.js . -COPY --from=builder /app/database-options.js . -COPY --from=builder /app/dist ./dist - -USER node -EXPOSE 4000 - -CMD ["npm", "start"] diff --git a/api-js/README.md b/api-js/README.md deleted file mode 100644 index ff0b068325..0000000000 --- a/api-js/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# Tracker API - -The Tracker API is exclusively focused on serving data, rather than HTML. It is a GraphQL API, chosen because of its [composability](https://en.wikipedia.org/wiki/Composability), [legibility](https://www.ribbonfarm.com/2010/07/26/a-big-little-idea-called-legibility/) and for the way it [enables both security and security automation](https://www.youtube.com/watch?v=gqvyCdyp3Nw). -It is built with the [Express webserver](https://expressjs.com/) using the [express-graphql middleware](https://github.com/graphql/express-graphql), and follows the [Relay specifications for pagination](https://relay.dev/graphql/connections.htm). - -#### Installing Dependencies - -```shell -npm install -``` -#### Running API Server - -In accordance with the [12Factor app](https://12factor.net) philosophy, the server [draws it's config from the environment](https://12factor.net/config). It does based on a `.env` file that should exist in the root of the API folder which can be created with the following command, obviously modifying the test values shown to suit your setup. - -```bash -cat <<'EOF' > test.env -DB_PASS=test -DB_URL=http://localhost:8529 -DB_NAME=track_dmarc -AUTHENTICATED_KEY=12341234 -SIGN_IN_KEY=12341234 -NOTIFICATION_API_KEY=asdf1234 -NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca -NOTIFICATION_AUTHENTICATE_EMAIL_ID=test_id -NOTIFICATION_AUTHENTICATE_TEXT_ID=test_id -NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN=test_id -NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR=test_id -NOTIFICATION_ORG_INVITE_EN=test_id -NOTIFICATION_ORG_INVITE_FR=test_id -NOTIFICATION_PASSWORD_RESET_EN=test_id -NOTIFICATION_PASSWORD_RESET_FR=test_id -NOTIFICATION_TWO_FACTOR_CODE_EN=test_id -NOTIFICATION_TWO_FACTOR_CODE_FR=test_id -NOTIFICATION_VERIFICATION_EMAIL_EN=test_id -NOTIFICATION_VERIFICATION_EMAIL_FR=test_id -DMARC_REPORT_API_SECRET=somesecretvalue -TOKEN_HASH=somehash -DMARC_REPORT_API_TOKEN=uuid-like-string -DMARC_REPORT_API_URL=http://localhost:4001/graphql -DEPTH_LIMIT=5 -COST_LIMIT=100 -SCALAR_COST=1 -OBJECT_COST=1 -LIST_FACTOR=1 -CIPHER_KEY=1234averyveryveryveryverylongkey -DNS_SCANNER_ENDPOINT=dns.scanners -HTTPS_SCANNER_ENDPOINT=https.scanners -SSL_SCANNER_ENDPOINT=ssl.scanners -REDIS_PORT_NUMBER=6379 -REDIS_DOMAIN_NAME=localhost -DKIM_SCAN_CHANNEL=scan/dkim -DMARC_SCAN_CHANNEL=scan/dmarc -HTTPS_SCAN_CHANNEL=scan/https -SPF_SCAN_CHANNEL=scan/spf -SSL_SCAN_CHANNEL=scan/ssl -EOF -``` -With that defined you can start the server with these commands. - -```shell -# Compile the scripts -npm run build -# Run the server -npm start -``` - -An online IDE will be accessible at [localhost:4000/graphql](http://localhost:4000/graphql) allowing you to explore the API. - -### Dev Workflow - -#### Install Dev Dependencies -```shell -npm install -``` - -#### Running Server with Nodemon -```shell -npm run dev-start -``` - -#### Running Tests - -The tests require a copy of [ArangoDB](https://www.arangodb.com/) to be running locally. ArangoDB should have it's own .env file, and the value of the root password should align with the value of `DB_PASS` in the APIs `test.env` file. - -```bash -# Write the arango test credentials into an env file: -echo ARANGO_ROOT_PASSWORD=test > arangodb.env -# Run a detached arangodb container using the root password from the env: -docker run -d -p=8529:8529 --env-file arangodb.env --name=arango arangodb -``` - -The tests also requires a copy of [Redis](https://redis.io/) to be running locally. -```bash -docker run -d -p=6379:6379 --name=redis redis -``` - -With the databases running, we need create the environment variables the application needs, but with some test appropriate values. We can do that by creating `test.env` in the API root directory with the following command. - -```bash -cat <<'EOF' > test.env -DB_PASS=test -DB_URL=http://localhost:8529 -DB_NAME=track_dmarc -AUTHENTICATED_KEY=12341234 -SIGN_IN_KEY=12341234 -NOTIFICATION_API_KEY=asdf1234 -NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca -NOTIFICATION_AUTHENTICATE_EMAIL_ID=some-template-id -NOTIFICATION_AUTHENTICATE_TEXT_ID=some-template-id -NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN=some-template-id -NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR=some-template-id -NOTIFICATION_ORG_INVITE_EN=some-template-id -NOTIFICATION_ORG_INVITE_FR=some-template-id -NOTIFICATION_PASSWORD_RESET_EN=some-template-id -NOTIFICATION_PASSWORD_RESET_FR=some-template-id -NOTIFICATION_TWO_FACTOR_CODE_EN=some-template-id -NOTIFICATION_TWO_FACTOR_CODE_FR=some-template-id -NOTIFICATION_VERIFICATION_EMAIL_EN=some-template-id -NOTIFICATION_VERIFICATION_EMAIL_FR=some-template-id -DMARC_REPORT_API_SECRET=somesecretvalue -TOKEN_HASH=somehash -DMARC_REPORT_API_TOKEN=uuid-like-string -DMARC_REPORT_API_URL=http://localhost:4001/graphql -DEPTH_LIMIT=5 -COST_LIMIT=100 -SCALAR_COST=1 -OBJECT_COST=1 -LIST_FACTOR=1 -REDIS_PORT_NUMBER=6379 -REDIS_DOMAIN_NAME=localhost -DKIM_SCAN_CHANNEL=scan/dkim -DMARC_SCAN_CHANNEL=scan/dmarc -HTTPS_SCAN_CHANNEL=scan/https -SPF_SCAN_CHANNEL=scan/spf -SSL_SCAN_CHANNEL=scan/ssl -EOF -``` - -Finally, run the tests. - -```bash -npm test -``` - -#### Checking Test Coverage - -```shell -npm run test-coverage -``` - -#### Running ESLint - -```shell -npm run lint -``` - -#### Formatting Code with Prettier -```shell -npm run prettier -``` diff --git a/api-js/cloudbuild.yaml b/api-js/cloudbuild.yaml deleted file mode 100644 index eb0ad13a85..0000000000 --- a/api-js/cloudbuild.yaml +++ /dev/null @@ -1,191 +0,0 @@ -steps: -- name: 'gcr.io/cloud-builders/docker' - id: start_arango - entrypoint: /bin/sh - args: - [ - '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', - ] - -- name: mikewilliamson/wait-for - id: wait_arango - args: ['arangodb:8529'] - -- name: 'gcr.io/cloud-builders/docker' - id: start_redis - entrypoint: /bin/sh - args: - [ - '-c', - 'docker run -d --network=cloudbuild -p=6379:6379 --name=redis redis', - ] - -- name: mikewilliamson/wait-for - id: wait_redis - args: ['redis:6379'] - -- name: node:alpine - id: install - dir: api-js - entrypoint: npm - args: ['ci', '--no-optional'] - -- name: node:alpine - id: lint - dir: api-js - entrypoint: npm - args: ['run', lint] - -- name: node:alpine - id: test-subscriptions - dir: api-js - entrypoint: npm - args: ['run', 'test-subscriptions'] - env: - - DB_PASS=$_DB_PASS - - DB_URL=$_DB_URL - - DB_NAME=$_DB_NAME - - AUTHENTICATED_KEY=$_AUTHENTICATED_KEY - - REFRESH_KEY=$_REFRESH_KEY - - SIGN_IN_KEY=$_SIGN_IN_KEY - - AUTH_TOKEN_EXPIRY=$_AUTH_TOKEN_EXPIRY - - REFRESH_TOKEN_EXPIRY=$_REFRESH_TOKEN_EXPIRY - - NOTIFICATION_API_KEY=$_NOTIFICATION_API_KEY - - NOTIFICATION_API_URL=$_NOTIFICATION_API_URL - - NOTIFICATION_AUTHENTICATE_EMAIL_ID=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_AUTHENTICATE_TEXT_ID=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_PASSWORD_RESET_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_PASSWORD_RESET_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_TWO_FACTOR_CODE_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_TWO_FACTOR_CODE_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_VERIFICATION_EMAIL_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_VERIFICATION_EMAIL_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - DMARC_REPORT_API_SECRET=$_DMARC_REPORT_API_SECRET - - TOKEN_HASH=$_TOKEN_HASH - - DMARC_REPORT_API_TOKEN=$_DMARC_REPORT_API_TOKEN - - DMARC_REPORT_API_URL=$_DMARC_REPORT_API_URL - - DEPTH_LIMIT=$_DEPTH_LIMIT - - COST_LIMIT=$_COST_LIMIT - - SCALAR_COST=$_SCALAR_COST - - OBJECT_COST=$_OBJECT_COST - - LIST_FACTOR=$_LIST_FACTOR - - CIPHER_KEY=$_CIPHER_KEY - - DNS_SCANNER_ENDPOINT=$_DNS_SCANNER_ENDPOINT - - HTTPS_SCANNER_ENDPOINT=$_HTTPS_SCANNER_ENDPOINT - - SSL_SCANNER_ENDPOINT=$_SSL_SCANNER_ENDPOINT - - TRACING_ENABLED=$_TRACING_ENABLED - - REDIS_PORT_NUMBER=$_REDIS_PORT_NUMBER - - REDIS_DOMAIN_NAME=$_REDIS_DOMAIN_NAME - - DKIM_SCAN_CHANNEL=$_DKIM_SCAN_CHANNEL - - DMARC_SCAN_CHANNEL=$_DMARC_SCAN_CHANNEL - - HTTPS_SCAN_CHANNEL=$_HTTPS_SCAN_CHANNEL - - SPF_SCAN_CHANNEL=$_SPF_SCAN_CHANNEL - - SSL_SCAN_CHANNEL=$_SSL_SCAN_CHANNEL - -- name: node:alpine - id: test - dir: api-js - entrypoint: npm - args: ['run', 'test-remainder'] - env: - - DB_PASS=$_DB_PASS - - DB_URL=$_DB_URL - - DB_NAME=$_DB_NAME - - AUTHENTICATED_KEY=$_AUTHENTICATED_KEY - - REFRESH_KEY=$_REFRESH_KEY - - SIGN_IN_KEY=$_SIGN_IN_KEY - - AUTH_TOKEN_EXPIRY=$_AUTH_TOKEN_EXPIRY - - REFRESH_TOKEN_EXPIRY=$_REFRESH_TOKEN_EXPIRY - - NOTIFICATION_API_KEY=$_NOTIFICATION_API_KEY - - NOTIFICATION_API_URL=$_NOTIFICATION_API_URL - - NOTIFICATION_AUTHENTICATE_EMAIL_ID=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_AUTHENTICATE_TEXT_ID=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_ORG_INVITE_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_PASSWORD_RESET_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_PASSWORD_RESET_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_TWO_FACTOR_CODE_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_TWO_FACTOR_CODE_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_VERIFICATION_EMAIL_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - - NOTIFICATION_VERIFICATION_EMAIL_FR=$_NOTIFICATION_TEST_TEMPLATE_ID - - DMARC_REPORT_API_SECRET=$_DMARC_REPORT_API_SECRET - - TOKEN_HASH=$_TOKEN_HASH - - DMARC_REPORT_API_TOKEN=$_DMARC_REPORT_API_TOKEN - - DMARC_REPORT_API_URL=$_DMARC_REPORT_API_URL - - DEPTH_LIMIT=$_DEPTH_LIMIT - - COST_LIMIT=$_COST_LIMIT - - SCALAR_COST=$_SCALAR_COST - - OBJECT_COST=$_OBJECT_COST - - LIST_FACTOR=$_LIST_FACTOR - - CIPHER_KEY=$_CIPHER_KEY - - DNS_SCANNER_ENDPOINT=$_DNS_SCANNER_ENDPOINT - - HTTPS_SCANNER_ENDPOINT=$_HTTPS_SCANNER_ENDPOINT - - SSL_SCANNER_ENDPOINT=$_SSL_SCANNER_ENDPOINT - - TRACING_ENABLED=$_TRACING_ENABLED - - REDIS_PORT_NUMBER=$_REDIS_PORT_NUMBER - - REDIS_DOMAIN_NAME=$_REDIS_DOMAIN_NAME - - DKIM_SCAN_CHANNEL=$_DKIM_SCAN_CHANNEL - - DMARC_SCAN_CHANNEL=$_DMARC_SCAN_CHANNEL - - HTTPS_SCAN_CHANNEL=$_HTTPS_SCAN_CHANNEL - - SPF_SCAN_CHANNEL=$_SPF_SCAN_CHANNEL - - SSL_SCAN_CHANNEL=$_SSL_SCAN_CHANNEL - -- name: node:alpine - id: lingui-extract - dir: api-js - entrypoint: npm - args: ['run', 'extract'] - -- name: node:alpine - id: lingui-compile - dir: api-js - entrypoint: npm - args: ['run', 'compile'] - -- name: 'gcr.io/cloud-builders/docker' - id: generate-image-name - entrypoint: 'bash' - dir: api-js - args: - - '-c' - - | - echo "gcr.io/$PROJECT_ID/api-js:$BRANCH_NAME-$SHORT_SHA-$(date +%s)" > /workspace/imagename - -- name: 'gcr.io/cloud-builders/docker' - id: build-if-master - entrypoint: 'bash' - dir: api-js - args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then - image=$(cat /workspace/imagename) - docker build -t $image . - else - exit 0 - fi - -- name: 'gcr.io/cloud-builders/docker' - id: push-if-master - entrypoint: 'bash' - args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then - image=$(cat /workspace/imagename) - docker push $image - else - exit 0 - fi -timeout: 1200s -options: - machineType: 'E2_HIGHCPU_8' diff --git a/api-js/database-options.js b/api-js/database-options.js deleted file mode 100644 index fef79e08ff..0000000000 --- a/api-js/database-options.js +++ /dev/null @@ -1,213 +0,0 @@ -export const databaseOptions = ({ rootPass }) => [ - { type: 'user', username: 'root', password: rootPass }, - { - type: 'documentcollection', - name: 'users', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'organizations', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'domains', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'dkim', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'dkimResults', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'dmarc', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'spf', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'https', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'ssl', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'dkimGuidanceTags', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'dmarcGuidanceTags', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'spfGuidanceTags', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'httpsGuidanceTags', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'sslGuidanceTags', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'chartSummaries', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'dmarcSummaries', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'aggregateGuidanceTags', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'scanSummaryCriteria', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'documentcollection', - name: 'chartSummaryCriteria', - options: { replicationfactor: 3, writeconcern: 1, numberofshards: 6 }, - }, - { - type: 'documentcollection', - name: 'scanSummaries', - options: { replicationfactor: 3, writeconcern: 1, numberofshards: 6 }, - }, - { - type: 'edgecollection', - name: 'affiliations', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'claims', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'domainsDKIM', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'dkimToDkimResults', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'domainsDMARC', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'domainsSPF', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'domainsHTTPS', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'domainsSSL', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'ownership', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'edgecollection', - name: 'domainsToDmarcSummaries', - options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, - }, - { - type: 'delimiteranalyzer', - name: 'space-delimiter-analyzer', - delimiter: ' ', - }, - { - type: 'searchview', - name: 'domainSearch', - options: { - links: { - domains: { - fields: { - domain: { analyzers: ['space-delimiter-analyzer'] }, - }, - }, - }, - }, - }, - { - type: 'searchview', - name: 'organizationSearch', - options: { - links: { - organizations: { - fields: { - orgDetails: { - fields: { - en: { - fields: { - acronym: { analyzers: ['text_en'] }, - name: { analyzers: ['text_en'] }, - }, - }, - fr: { - fields: { - acronym: { analyzers: ['text_fr'] }, - name: { analyzers: ['text_fr'] }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - type: 'searchview', - name: 'userSearch', - options: { - links: { - users: { - fields: { - displayName: { analyzers: ['text_en'] }, - userName: { analyzers: ['text_en'] }, - }, - }, - }, - }, - }, -] diff --git a/api-js/index.js b/api-js/index.js deleted file mode 100644 index c7dc8e3e51..0000000000 --- a/api-js/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import './src/env' -import { ensure } from 'arango-tools' -import { Server } from './src/server' -import { databaseOptions } from './database-options' -import Redis from 'ioredis' -import { RedisPubSub } from 'graphql-redis-subscriptions' - -const { - PORT = 4000, - DB_PASS: rootPass, - DB_URL: url, - DB_NAME: databaseName, - DEPTH_LIMIT: maxDepth, - COST_LIMIT: complexityCost, - SCALAR_COST: scalarCost, - OBJECT_COST: objectCost, - LIST_FACTOR: listFactor, - TRACING_ENABLED: tracing, - REDIS_PORT_NUMBER, - REDIS_DOMAIN_NAME, -} = process.env - -;(async () => { - const { query, collections, transaction } = await ensure({ - type: 'database', - name: databaseName, - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - }) - - // Connect With Redis - const options = { - host: REDIS_DOMAIN_NAME, - port: REDIS_PORT_NUMBER, - } - - const pubsubs = { - dkimPubSub: new RedisPubSub({ - publisher: new Redis(options), - subscriber: new Redis(options), - }), - dmarcPubSub: new RedisPubSub({ - publisher: new Redis(options), - subscriber: new Redis(options), - }), - spfPubSub: new RedisPubSub({ - publisher: new Redis(options), - subscriber: new Redis(options), - }), - httpsPubSub: new RedisPubSub({ - publisher: new Redis(options), - subscriber: new Redis(options), - }), - sslPubSub: new RedisPubSub({ - publisher: new Redis(options), - subscriber: new Redis(options), - }), - } - - const server = await Server({ - arango: { - db: databaseName, - url, - as: { - username: 'root', - password: rootPass, - }, - }, - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, - tracing, - context: { - query, - collections, - transaction, - pubsubs, - }, - }) - - await server.listen(PORT, (err) => { - if (err) throw err - console.log(`🚀 Server ready at http://localhost:${PORT}/graphql`) - console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}/graphql`) - }) -})() diff --git a/api-js/jest.config.js b/api-js/jest.config.js deleted file mode 100644 index f76d1efeb1..0000000000 --- a/api-js/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - setupFiles: ['/src/setupEnv.js'], - collectCoverage: false, - collectCoverageFrom: ['src/**/*.js'], - coveragePathIgnorePatterns: [ - 'node_modules', - 'test-config', - 'jestGlobalMocks.js', - '.module.js', - 'locale', - 'index.js', - 'env.js', - ], - testTimeout: 120000, -} diff --git a/api-js/package-lock.json b/api-js/package-lock.json deleted file mode 100644 index 12e0bd4021..0000000000 --- a/api-js/package-lock.json +++ /dev/null @@ -1,30656 +0,0 @@ -{ - "name": "tracker-api", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "tracker-api", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@lingui/core": "^3.11.1", - "apollo-server": "^3.3.0", - "apollo-server-core": "^3.3.0", - "apollo-server-express": "^3.3.0", - "arango-express": "^1.0.0", - "arango-tools": "^0.5.0", - "arangojs": "^7.5.0", - "bcryptjs": "^2.4.3", - "cookie-parser": "^1.4.5", - "cors": "^2.8.5", - "crypto": "^1.0.1", - "dataloader": "^2.0.0", - "dotenv-safe": "^8.2.0", - "express": "^4.17.1", - "express-request-language": "^1.1.15", - "graphql": "^15.5.3", - "graphql-depth-limit": "^1.1.0", - "graphql-playground-middleware-express": "^1.7.22", - "graphql-redis-subscriptions": "^2.4.0", - "graphql-relay": "^0.9.0", - "graphql-scalars": "^1.10.1", - "graphql-subscriptions": "^1.2.1", - "graphql-validation-complexity": "^0.4.2", - "graphql-voyager": "^1.0.0-rc.31", - "ioredis": "^4.27.9", - "isomorphic-fetch": "^3.0.0", - "jsonwebtoken": "^8.5.1", - "moment": "^2.29.1", - "notifications-node-client": "^5.1.0", - "subscriptions-transport-ws": "^0.9.19", - "url-slug": "^3.0.2", - "uuid": "^8.3.2", - "validator": "^13.6.0" - }, - "devDependencies": { - "@babel/cli": "^7.15.4", - "@babel/core": "^7.15.5", - "@babel/node": "^7.15.4", - "@babel/preset-env": "^7.15.6", - "@jest/test-sequencer": "^27.2.0", - "@lingui/cli": "^3.11.1", - "@lingui/loader": "^3.11.1", - "@lingui/macro": "^3.11.1", - "babel-core": "^7.0.0-bridge.0", - "babel-jest": "^27.2.0", - "babel-plugin-macros": "^3.1.0", - "babel-polyfill": "^6.26.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.4.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "jest": "^27.2.0", - "jest-fetch-mock": "^3.0.3", - "jest-matcher-utils": "^27.2.0", - "nodemon": "^2.0.12", - "prettier": "^2.4.1", - "supertest": "^6.1.6" - } - }, - "node_modules/@apollo/protobufjs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", - "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", - "hasInstallScript": true, - "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/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - }, - "bin": { - "apollo-pbjs": "bin/pbjs", - "apollo-pbts": "bin/pbts" - } - }, - "node_modules/@apollo/protobufjs/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" - }, - "node_modules/@apollographql/apollo-tools": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.1.tgz", - "integrity": "sha512-ZII+/xUFfb9ezDU2gad114+zScxVFMVlZ91f8fGApMzlS1kkqoyLnC4AJaQ1Ya/X+b63I20B4Gd+eCL8QuB4sA==", - "engines": { - "node": ">=8", - "npm": ">=6" - } - }, - "node_modules/@apollographql/graphql-playground-html": { - "version": "1.6.29", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", - "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", - "dependencies": { - "xss": "^1.0.8" - } - }, - "node_modules/@babel/cli": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.15.4.tgz", - "integrity": "sha512-9RhhQ7tgKRcSO/jI3rNLxalLSk30cHqeM8bb+nGOJTyYBDpkoXw/A9QHZ2SYjlslAt4tr90pZQGIEobwWHSIDw==", - "dev": true, - "dependencies": { - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - }, - "bin": { - "babel": "bin/babel.js", - "babel-external-helpers": "bin/babel-external-helpers.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.2", - "chokidar": "^3.4.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", - "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.5", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz", - "integrity": "sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.15.4.tgz", - "integrity": "sha512-P8o7JP2Mzi0SdC6eWr1zF+AEYvrsZa7GSY1lTayjF5XJhVH0kjLYUZPvTMflP7tBgZoe9gIhTa60QwFpqh/E0Q==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.4.tgz", - "integrity": "sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", - "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.15.4.tgz", - "integrity": "sha512-J14f/vq8+hdC2KoWLIQSsGrC9EFBKE4NFts8pfMpymfApds+fPqR30AOUWc4tyr56h9l/GA1Sxv2q3dLZWbQ/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz", - "integrity": "sha512-v53MxgvMK/HCwckJ1bZrq6dNKlmwlyRNYM6ypaRTdXWGOE2c1/SCa6dL/HimhPulGhZKw9W0QhREM583F/t0vQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-wrap-function": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz", - "integrity": "sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.15.4.tgz", - "integrity": "sha512-Y2o+H/hRV5W8QhIfTpRIBwl57y8PrZt6JM3V8FOo5qarjshHItyH5lXlpMfBfmBefOqSCpKZs/6Dxqp0E/U+uw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/node": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.15.4.tgz", - "integrity": "sha512-UZue+j8p5aKTaVjvy5psYmqLHqmz+9cIboAFoa97S1xeZyUr0gT6KzXB8ZkfBIsP/u79biOdjGHVXBXnW3rVfw==", - "dev": true, - "dependencies": { - "@babel/register": "^7.15.3", - "commander": "^4.0.1", - "core-js": "^3.16.0", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.13.4", - "v8flags": "^3.1.1" - }, - "bin": { - "babel-node": "bin/babel-node.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/parser": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", - "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz", - "integrity": "sha512-eBnpsl9tlhPhpI10kU06JHnrYXwg3+V6CaP2idsCXNef0aeslpqyITXQ74Vfk5uHgY7IG7XP0yIH8b42KSzHog==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4", - "@babel/plugin-proposal-optional-chaining": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.4.tgz", - "integrity": "sha512-2zt2g5vTXpMC3OmK6uyjvdXptbhBXfA77XGrd3gh93zwG8lZYBLOBImiGBEG0RANu3JqKEACCz5CGk73OJROBw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.15.4", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.15.4.tgz", - "integrity": "sha512-M682XWrrLNk3chXCjoPUQWOyYsB93B9z3mRyjtqqYJWDf2mfCdIYgDrA11cgNVhAQieaq6F2fn2f3wI0U4aTjA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", - "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", - "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", - "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", - "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", - "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", - "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.15.6.tgz", - "integrity": "sha512-qtOHo7A1Vt+O23qEAX+GdBpqaIuD3i9VRrWgCJeq7WO6H2d14EK3q11urj5Te2MAeK97nMiIdRpwd/ST4JFbNg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", - "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", - "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", - "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.15.4.tgz", - "integrity": "sha512-X0UTixkLf0PCCffxgu5/1RQyGGbgZuKoI+vXP4iSbJSYwPb7hu06omsFGBvQ9lJEvwgrxHdS8B5nbfcd8GyUNA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", - "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", - "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", - "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.15.4.tgz", - "integrity": "sha512-Yjvhex8GzBmmPQUvpXRPWQ9WnxXgAFuZSrqOK/eJlOGIXwvv8H3UEdUigl1gb/bnjTrln+e8bkZUYCBt/xYlBg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", - "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", - "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", - "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", - "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.15.4.tgz", - "integrity": "sha512-DRTY9fA751AFBDh2oxydvVm4SYevs5ILTWLs6xKXps4Re/KG5nfUkr+TdHCrRWB8C69TlzVgA9b3RmGWmgN9LA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", - "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", - "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", - "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", - "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.4.tgz", - "integrity": "sha512-qg4DPhwG8hKp4BbVDvX1s8cohM8a6Bvptu4l6Iingq5rW+yRUAhe/YRup/YcW2zCOlrysEWVhftIcKzrEZv3sA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.15.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.15.4.tgz", - "integrity": "sha512-fJUnlQrl/mezMneR72CKCgtOoahqGJNVKpompKwzv3BrEXdlPspTcyxrZ1XmDTIr9PpULrgEQo3qNKp6dW7ssw==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", - "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", - "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", - "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", - "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", - "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", - "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", - "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", - "dev": true, - "dependencies": { - "regenerator-transform": "^0.14.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", - "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", - "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", - "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", - "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", - "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", - "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", - "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", - "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.15.6.tgz", - "integrity": "sha512-L+6jcGn7EWu7zqaO2uoTDjjMBW+88FXzV8KvrBl2z6MtRNxlsmUNRlZPaNNPUTgqhyC5DHNFk/2Jmra+ublZWw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.15.4", - "@babel/plugin-proposal-async-generator-functions": "^7.15.4", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-class-static-block": "^7.15.4", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-json-strings": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-object-rest-spread": "^7.15.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.15.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@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/plugin-transform-arrow-functions": "^7.14.5", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/plugin-transform-block-scoped-functions": "^7.14.5", - "@babel/plugin-transform-block-scoping": "^7.15.3", - "@babel/plugin-transform-classes": "^7.15.4", - "@babel/plugin-transform-computed-properties": "^7.14.5", - "@babel/plugin-transform-destructuring": "^7.14.7", - "@babel/plugin-transform-dotall-regex": "^7.14.5", - "@babel/plugin-transform-duplicate-keys": "^7.14.5", - "@babel/plugin-transform-exponentiation-operator": "^7.14.5", - "@babel/plugin-transform-for-of": "^7.15.4", - "@babel/plugin-transform-function-name": "^7.14.5", - "@babel/plugin-transform-literals": "^7.14.5", - "@babel/plugin-transform-member-expression-literals": "^7.14.5", - "@babel/plugin-transform-modules-amd": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.15.4", - "@babel/plugin-transform-modules-systemjs": "^7.15.4", - "@babel/plugin-transform-modules-umd": "^7.14.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9", - "@babel/plugin-transform-new-target": "^7.14.5", - "@babel/plugin-transform-object-super": "^7.14.5", - "@babel/plugin-transform-parameters": "^7.15.4", - "@babel/plugin-transform-property-literals": "^7.14.5", - "@babel/plugin-transform-regenerator": "^7.14.5", - "@babel/plugin-transform-reserved-words": "^7.14.5", - "@babel/plugin-transform-shorthand-properties": "^7.14.5", - "@babel/plugin-transform-spread": "^7.14.6", - "@babel/plugin-transform-sticky-regex": "^7.14.5", - "@babel/plugin-transform-template-literals": "^7.14.5", - "@babel/plugin-transform-typeof-symbol": "^7.14.5", - "@babel/plugin-transform-unicode-escapes": "^7.14.5", - "@babel/plugin-transform-unicode-regex": "^7.14.5", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.15.6", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.2", - "babel-plugin-polyfill-regenerator": "^0.2.2", - "core-js-compat": "^3.16.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.15.3.tgz", - "integrity": "sha512-mj4IY1ZJkorClxKTImccn4T81+UKTo4Ux0+OFSV9hME1ooqS9UV+pJ6BjD0qXPK4T3XW/KNa79XByjeEMZz+fw==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.0", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", - "dev": true, - "dependencies": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "cosmiconfig": ">=6" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@f/animate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@f/animate/-/animate-1.0.1.tgz", - "integrity": "sha1-oDE5itrfmgvTpWOYzskx+HfYhIU=", - "dependencies": { - "@f/elapsed-time": "^1.0.0", - "@f/raf": "^1.0.0", - "@f/tween": "^1.0.0" - } - }, - "node_modules/@f/elapsed-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@f/elapsed-time/-/elapsed-time-1.0.0.tgz", - "integrity": "sha1-ageaYQSocni/W0CARE7wLRtZVEk=", - "dependencies": { - "@f/timestamp": "^1.0.0" - } - }, - "node_modules/@f/map-obj": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@f/map-obj/-/map-obj-1.2.2.tgz", - "integrity": "sha1-2an4vXbKoq4RtjPdok2cbMzB5g0=" - }, - "node_modules/@f/raf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@f/raf/-/raf-1.0.3.tgz", - "integrity": "sha1-Mt3KN940WyDIw4QwGMxRuPiXkU0=" - }, - "node_modules/@f/timestamp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@f/timestamp/-/timestamp-1.0.0.tgz", - "integrity": "sha1-MqkWbiUW5cy5sPz9yJIjgZcQ6Iw=" - }, - "node_modules/@f/tween": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@f/tween/-/tween-1.0.1.tgz", - "integrity": "sha1-GK73nEl15UQVrfMm5LXg0FPSB/A=", - "dependencies": { - "@f/map-obj": "^1.2.2" - } - }, - "node_modules/@graphql-tools/merge": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.1.2.tgz", - "integrity": "sha512-kFLd4kKNJXYXnKIhM8q9zgGAtbLmsy3WmGdDxYq3YHBJUogucAxnivQYyRIseUq37KGmSAIWu3pBQ23TKGsGOw==", - "dependencies": { - "@graphql-tools/utils": "^8.2.2", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@graphql-tools/mock": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.3.1.tgz", - "integrity": "sha512-iJ3GeQ10Vqa0Tg4QJHiulxUUI4r84RAvltM3Sc+XPj07QlrLzMHOHO/goO7FC4TN2/HVncj7pWHwrmLPT9du/Q==", - "dependencies": { - "@graphql-tools/schema": "^8.2.0", - "@graphql-tools/utils": "^8.2.0", - "fast-json-stable-stringify": "^2.1.0", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@graphql-tools/schema": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.2.0.tgz", - "integrity": "sha512-ufmI5mJQa8NJczzfkh0pUttKvspqDcT5LLakA3jUmOrrE4d4NVj6onZlazdTzF5sAepSNqanFnwhrxZpCAJMKg==", - "dependencies": { - "@graphql-tools/merge": "^8.1.0", - "@graphql-tools/utils": "^8.2.0", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@graphql-tools/utils": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.2.2.tgz", - "integrity": "sha512-29FFY5U4lpXuBiW9dRvuWnBVwGhWbGLa2leZcAMU/Pz47Cr/QLZGVgpLBV9rt+Gbs7wyIJM7t7EuksPs0RDm3g==", - "dependencies": { - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.2.0.tgz", - "integrity": "sha512-35z+RqsK2CCgNxn+lWyK8X4KkaDtfL4BggT7oeZ0JffIiAiEYFYPo5B67V50ZubqDS1ehBrdCR2jduFnIrZOYw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.2.0", - "jest-util": "^27.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.2.0.tgz", - "integrity": "sha512-E/2NHhq+VMo18DpKkoty8Sjey8Kps5Cqa88A8NP757s6JjYqPdioMuyUBhDiIOGCdQByEp0ou3jskkTszMS0nw==", - "dev": true, - "dependencies": { - "@jest/console": "^27.2.0", - "@jest/reporters": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.1.1", - "jest-config": "^27.2.0", - "jest-haste-map": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.2.0", - "jest-resolve-dependencies": "^27.2.0", - "jest-runner": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "jest-watcher": "^27.2.0", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/@jest/core/node_modules/jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/@jest/environment": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.2.0.tgz", - "integrity": "sha512-iPWmQI0wRIYSZX3wKu4FXHK4eIqkfq6n1DCDJS+v3uby7SOXrHvX4eiTBuEdSvtDRMTIH2kjrSkjHf/F9JIYyQ==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "jest-mock": "^27.1.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.0.tgz", - "integrity": "sha512-gSu3YHvQOoVaTWYGgHFB7IYFtcF2HBzX4l7s47VcjvkUgL4/FBnE20x7TNLa3W6ABERtGd5gStSwsA8bcn+c4w==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.2.0", - "jest-mock": "^27.1.1", - "jest-util": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.2.0.tgz", - "integrity": "sha512-raqk9Gf9WC3hlBa57rmRmJfRl9hom2b+qEE/ifheMtwn5USH5VZxzrHHOZg0Zsd/qC2WJ8UtyTwHKQAnNlDMdg==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.2.0", - "@jest/types": "^27.1.1", - "expect": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.0.tgz", - "integrity": "sha512-7wfkE3iRTLaT0F51h1mnxH3nQVwDCdbfgXiLuCcNkF1FnxXLH9utHqkSLIiwOTV1AtmiE0YagHbOvx4rnMP/GA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.2.0", - "jest-resolve": "^27.2.0", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.0.tgz", - "integrity": "sha512-JPPqn8h0RGr4HyeY1Km+FivDIjTFzDROU46iAvzVjD42ooGwYoqYO/MQTilhfajdz6jpVnnphFrKZI5OYrBONA==", - "dev": true, - "dependencies": { - "@jest/console": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.0.tgz", - "integrity": "sha512-PrqarcpzOU1KSAK7aPwfL8nnpaqTMwPe7JBPnaOYRDSe/C6AoJiL5Kbnonqf1+DregxZIRAoDg69R9/DXMGqXA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.2.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-runtime": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.2.0.tgz", - "integrity": "sha512-Q8Q/8xXIZYllk1AF7Ou5sV3egOZsdY/Wlv09CSbcexBRcC1Qt6lVZ7jRFAZtbHsEEzvOCyFEC4PcrwKwyjXtCg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.1.1", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.2.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/@jest/transform/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/@jest/types": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", - "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@josephg/resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", - "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" - }, - "node_modules/@lingui/babel-plugin-extract-messages": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.11.1.tgz", - "integrity": "sha512-MAsZ0BYIsHh08dptT7bA6Jsh1ixO1sBU8eNDtobkZaZ78SXIUMUYCy9e3T9D/RYpecgDGaFUf2djctTqguMgmQ==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.11.1", - "mkdirp": "^1.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@lingui/cli": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.11.1.tgz", - "integrity": "sha512-piKjrGjiornzX18Lt6EhyICAHEGH9wio0KaOXKyCjHqPw8sQnC4AZv0iyZqTACVYL+0ROsrtNd/xgDMYNSQgeA==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.11.6", - "@babel/parser": "^7.11.5", - "@babel/plugin-syntax-jsx": "^7.10.4", - "@babel/runtime": "^7.11.2", - "@babel/types": "^7.11.5", - "@lingui/babel-plugin-extract-messages": "^3.11.1", - "@lingui/conf": "^3.11.1", - "babel-plugin-macros": "^3.0.1", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", - "chokidar": "3.5.1", - "cli-table": "^0.3.1", - "commander": "^6.1.0", - "date-fns": "^2.16.1", - "fs-extra": "^9.0.1", - "fuzzaldrin": "^2.1.0", - "glob": "^7.1.4", - "inquirer": "^7.3.3", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3", - "micromatch": "4.0.2", - "mkdirp": "^1.0.4", - "node-gettext": "^3.0.0", - "normalize-path": "^3.0.0", - "ora": "^5.1.0", - "papaparse": "^5.3.0", - "pkg-up": "^3.1.0", - "plurals-cldr": "^1.0.4", - "pofile": "^1.1.0", - "pseudolocale": "^1.1.0", - "ramda": "^0.27.1" - }, - "bin": { - "lingui": "lingui.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "babel-plugin-macros": "2 || 3", - "typescript": "2 || 3 || 4" - } - }, - "node_modules/@lingui/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@lingui/cli/node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@lingui/cli/node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/cli/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@lingui/cli/node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/@lingui/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@lingui/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@lingui/cli/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@lingui/cli/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/cli/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/cli/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/@lingui/cli/node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@lingui/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/cli/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/@lingui/conf": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.11.1.tgz", - "integrity": "sha512-WoEdtDAiI+TR7Gz2F7VMZQyIGZFP2b4qT3JO3gLuGzHY6a6DCqOMojqUuo6KHFQrUoUtebI/1Yn7gAxVH1xcWQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "jest-validate": "^26.5.2", - "lodash.get": "^4.4.2" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@lingui/conf/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@lingui/conf/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@lingui/conf/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@lingui/conf/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@lingui/conf/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/conf/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lingui/core": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.11.1.tgz", - "integrity": "sha512-qHMo47SbwFFx3IwXbMRafIMZH2tTYebrQhvAu5wH9OabI+bqbVWbTOLSAXzX/gDZqxMQWIrr2ndrah0aPZk6EQ==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@lingui/loader": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.11.1.tgz", - "integrity": "sha512-utBvBfbC/mupSShiEpUWBdTP7hC35iBJ3gji88NpY8MSr/6HZoaIw/eEv3n+jyygXYeDIHh2+xZjneOJ++xTsg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.11.1", - "@lingui/conf": "^3.11.1", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/@lingui/macro": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.11.1.tgz", - "integrity": "sha512-rSzvBs4Gasn6VO8msYA0/Bw285jUOBoLAVxURt6XaH45NXnJiWnDtEOD/DhQcuggDKbaWwz13lXOiRfUP0zG6g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.11.1", - "ramda": "^0.27.1" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "babel-plugin-macros": "2 || 3" - } - }, - "node_modules/@material-ui/core": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.4.tgz", - "integrity": "sha512-r8QFLSexcYZbnqy/Hn4v8xzmAJV41yaodUVjmbGLi1iGDLG3+W941hEtEiBmxTRRqv2BdK3r4ijILcqKmDv/Sw==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "@material-ui/system": "^3.0.0-alpha.0", - "@material-ui/utils": "^3.0.0-alpha.2", - "@types/jss": "^9.5.6", - "@types/react-transition-group": "^2.0.8", - "brcast": "^3.0.1", - "classnames": "^2.2.5", - "csstype": "^2.5.2", - "debounce": "^1.1.0", - "deepmerge": "^3.0.0", - "dom-helpers": "^3.2.1", - "hoist-non-react-statics": "^3.2.1", - "is-plain-object": "^2.0.4", - "jss": "^9.8.7", - "jss-camel-case": "^6.0.0", - "jss-default-unit": "^8.0.2", - "jss-global": "^3.0.0", - "jss-nested": "^6.0.1", - "jss-props-sort": "^6.0.0", - "jss-vendor-prefixer": "^7.0.0", - "normalize-scroll-left": "^0.1.2", - "popper.js": "^1.14.1", - "prop-types": "^15.6.0", - "react-event-listener": "^0.6.2", - "react-transition-group": "^2.2.1", - "recompose": "0.28.0 - 0.30.0", - "warning": "^4.0.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" - } - }, - "node_modules/@material-ui/core/node_modules/deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@material-ui/system": { - "version": "3.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", - "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "deepmerge": "^3.0.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" - } - }, - "node_modules/@material-ui/system/node_modules/deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@material-ui/utils": { - "version": "3.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", - "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "react-is": "^16.6.3" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" - } - }, - "node_modules/@material-ui/utils/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.2", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz", - "integrity": "sha512-Fb8WxUFOBQVl+CX4MWet5o7eCc6Pj04rXIwVKZ6h1NnqTo45eOQW6aWyhG25NIODvWFwTDMwBsYxrQ3imxpetg==", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^5.1.2", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" - }, - "node_modules/@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", - "dev": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true, - "peer": true - }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "node_modules/@types/jss": { - "version": "9.5.8", - "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz", - "integrity": "sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA==", - "dependencies": { - "csstype": "^2.0.0", - "indefinite-observable": "^1.0.1" - } - }, - "node_modules/@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/react": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.21.tgz", - "integrity": "sha512-GzzXCpOthOjXvrAUFQwU/svyxu658cwu00Q9ugujS4qc1zXgLFaO0kS2SLOaMWLt2Jik781yuHCWB7UcYdGAeQ==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-transition-group": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", - "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react/node_modules/csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz", - "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.31.1", - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/typescript-estree": "4.31.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz", - "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/visitor-keys": "4.31.1" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", - "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", - "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/visitor-keys": "4.31.1", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz", - "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.31.1", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accept-language": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", - "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=", - "dependencies": { - "bcp47": "^1.1.2", - "stable": "^0.1.6" - } - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "dependencies": { - "string-width": "^3.0.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/apollo-datasource": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.1.0.tgz", - "integrity": "sha512-ywcVjuWNo84eMB9uBOYygQI+00+Ne4ShyPIxJzT//sn1j1Fu3J+KStMNd6s1jyERWgjGZzxkiLn6nLmwsGymBg==", - "dependencies": { - "apollo-server-caching": "^3.1.0", - "apollo-server-env": "^4.0.3" - }, - "engines": { - "node": ">=12.0" - } - }, - "node_modules/apollo-graphql": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.3.tgz", - "integrity": "sha512-rcAl2E841Iko4kSzj4Pt3PRBitmyq1MvoEmpl04TQSpGnoVgl1E/ZXuLBYxMTSnEAm7umn2IsoY+c6Ll9U/10A==", - "dependencies": { - "core-js-pure": "^3.10.2", - "lodash.sortby": "^4.7.0", - "sha.js": "^2.4.11" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^14.2.1 || ^15.0.0" - } - }, - "node_modules/apollo-reporting-protobuf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.0.0.tgz", - "integrity": "sha512-jmCD+6gECt8KS7PxP460hztT/5URTbv2Kg0zgnR6iWPGce88IBmSUjcqf1Z6wJJq7Teb8Hu7WbyyMhn0vN5TxQ==", - "dependencies": { - "@apollo/protobufjs": "1.2.2" - } - }, - "node_modules/apollo-server": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.3.0.tgz", - "integrity": "sha512-vJE+lIOQMgno3IaJvwo66S53m0CU4fq7qAgnOCubitIGNKEHiUL6yXKL0rMGfOPZqeed2S2QWQrOsCGQDIudMw==", - "dependencies": { - "apollo-server-core": "^3.3.0", - "apollo-server-express": "^3.3.0", - "express": "^4.17.1" - }, - "peerDependencies": { - "graphql": "^15.3.0" - } - }, - "node_modules/apollo-server-caching": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.1.0.tgz", - "integrity": "sha512-bZ4bo0kSAsax9LbMQPlpuMTkQ657idF2ehOYe4Iw+8vj7vfAYa39Ii9IlaVAFMC1FxCYzLNFz+leZBm/Stn/NA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12.0" - } - }, - "node_modules/apollo-server-core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.3.0.tgz", - "integrity": "sha512-KmkzKVG3yjybouDyUX6Melv39u1EOFipvAKP17IlPis/TjVbubJmb6hkE0am/g2RipyhRvlpxAjHqPaCTXR1dQ==", - "dependencies": { - "@apollographql/apollo-tools": "^0.5.1", - "@apollographql/graphql-playground-html": "1.6.29", - "@graphql-tools/mock": "^8.1.2", - "@graphql-tools/schema": "^8.0.0", - "@graphql-tools/utils": "^8.0.0", - "@josephg/resolvable": "^1.0.0", - "apollo-datasource": "^3.1.0", - "apollo-graphql": "^0.9.0", - "apollo-reporting-protobuf": "^3.0.0", - "apollo-server-caching": "^3.1.0", - "apollo-server-env": "^4.0.3", - "apollo-server-errors": "^3.1.0", - "apollo-server-plugin-base": "^3.2.0", - "apollo-server-types": "^3.2.0", - "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.1.0", - "graphql-tag": "^2.11.0", - "loglevel": "^1.6.8", - "lru-cache": "^6.0.0", - "sha.js": "^2.4.11", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0" - } - }, - "node_modules/apollo-server-env": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.0.3.tgz", - "integrity": "sha512-B32+RUOM4GUJAwnQqQE1mT1BG7+VfW3a0A87Bp3gv/q8iNnhY2BIWe74Qn03pX8n27g3EGVCt0kcBuHhjG5ltA==", - "dependencies": { - "node-fetch": "^2.6.1" - }, - "engines": { - "node": ">=12.0" - } - }, - "node_modules/apollo-server-errors": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.1.0.tgz", - "integrity": "sha512-bUmobPEvtcBFt+OVHYqD390gacX/Cm5s5OI5gNZho8mYKAA6OjgnRlkm/Lti6NzniXVxEQyD5vjkC6Ox30mGFg==", - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0" - } - }, - "node_modules/apollo-server-express": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.3.0.tgz", - "integrity": "sha512-qJedh77IxbfT+HpYsDraC2CGdy08wiWTwoKYXjRK4S/DHbe94A4957/1blw4boYO4n44xRKQd1k6zxiixCp+XQ==", - "dependencies": { - "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.1", - "@types/cors": "2.8.12", - "@types/express": "4.17.13", - "@types/express-serve-static-core": "4.17.24", - "accepts": "^1.3.5", - "apollo-server-core": "^3.3.0", - "apollo-server-types": "^3.2.0", - "body-parser": "^1.19.0", - "cors": "^2.8.5", - "parseurl": "^1.3.3" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "express": "^4.17.1", - "graphql": "^15.3.0" - } - }, - "node_modules/apollo-server-plugin-base": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.2.0.tgz", - "integrity": "sha512-anjyiw79wxU4Cj2bYZFWQqZPjuaZ4mVJvxCoyvkFrNvjPua9dovCOfpng43C5NwdsqJpz78Vqs236eFM2QoeaA==", - "dependencies": { - "apollo-server-types": "^3.2.0" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0" - } - }, - "node_modules/apollo-server-types": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.2.0.tgz", - "integrity": "sha512-Fh7QP84ufDZHbLzoLyyxyzznlW8cpgEZYYkGsS1i36zY4VaAt5OUOp1f+FxWdLGehq0Arwb6D1W7y712IoZ/JQ==", - "dependencies": { - "apollo-reporting-protobuf": "^3.0.0", - "apollo-server-caching": "^3.1.0", - "apollo-server-env": "^4.0.3" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0" - } - }, - "node_modules/arango-express": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/arango-express/-/arango-express-1.0.0.tgz", - "integrity": "sha512-PNlQE5Z1ftZcwAHZgBY7Hx/18EE0CgRTr7XIFGgMVK87Ns0ym6kfReZyYH3J7zWrwzVwTjmA99gTRnW/28zFEQ==", - "dependencies": { - "arangojs": "^7.5.0" - } - }, - "node_modules/arango-tools": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.5.0.tgz", - "integrity": "sha512-Fb6gnfvtmqSH1lyHwiL3tKugOwRa8d1xPBrW+JF0ZUvZnAPS5PC2EHUYSzZoGurJa5DHHpkIz+azFeF9fbQniw==", - "dependencies": { - "arangojs": "^7.2.0", - "assign-deep": "^1.0.1" - } - }, - "node_modules/arangojs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.5.0.tgz", - "integrity": "sha512-9taMv73bo0O/bpFeqcT4xnYzu5yg+UZ9qGQ9SCAQXnKZZcOAwP1TixvOBv21il7XIzX28RCVoH+wp/u8Ajb8+Q==", - "dependencies": { - "@types/node": ">=13.13.4", - "es6-error": "^4.0.1", - "multi-part": "^3.0.0", - "x3-linkedlist": "1.2.0", - "xhr": "^2.4.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", - "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "node_modules/assign-deep": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", - "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", - "dependencies": { - "assign-symbols": "^2.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/assign-symbols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", - "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true, - "optional": true - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "optional": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.0.tgz", - "integrity": "sha512-bS2p+KGGVVmWXBa8+i6SO/xzpiz2Q/2LnqLbQknPKefWXVZ67YIjA4iXup/jMOEZplga9PpWn+wrdb3UdDwRaA==", - "dev": true, - "dependencies": { - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", - "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", - "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.2", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.4.tgz", - "integrity": "sha512-z3HnJE5TY/j4EFEa/qpQMSbcUJZ5JQi+3UFjXzn6pQCmIKc5Ug5j98SuYyH+m4xQnvKlMDIW4plLfgyVnd0IcQ==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.14.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", - "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.2.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - } - }, - "node_modules/babel-polyfill/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-polyfill/node_modules/regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", - "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^27.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "optional": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", - "dev": true, - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bcp47": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", - "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/boxen/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/brcast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.2.tgz", - "integrity": "sha512-f5XwwFCCuvgqP2nMH/hJ74FqnGmb4X3D+NC//HphxJzzhsZvSZa+Hk/syB7j3ZHpPDLMoYU8oBgviRWfNvEfKA==" - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz", - "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001254", - "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.830", - "escalade": "^3.1.1", - "node-releases": "^1.1.75" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "optional": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001257", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", - "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/change-emitter": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", - "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/chokidar/node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/chokidar/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/chokidar/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "optional": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", - "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", - "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", - "dev": true, - "dependencies": { - "colors": "1.0.3" - }, - "engines": { - "node": ">= 0.2.0" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/clipboard": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", - "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", - "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "optional": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "node_modules/colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/commonmark": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz", - "integrity": "sha512-fvt/NdOFKaL2gyhltSy6BC4LxbbxbnPxBMl923ittqO/JBM0wQHaoYZliE4tp26cRxX/ZZtRsJlZzQrVdUkXAA==", - "dependencies": { - "entities": "~2.0", - "mdurl": "~1.0.1", - "minimist": ">=1.2.2", - "string.prototype.repeat": "^0.2.0" - }, - "bin": { - "commonmark": "bin/commonmark" - }, - "engines": { - "node": "*" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/configstore/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", - "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", - "dependencies": { - "cookie": "0.4.0", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.3.tgz", - "integrity": "sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==", - "dev": true, - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.17.3.tgz", - "integrity": "sha512-+in61CKYs4hQERiADCJsdgewpdl/X0GhEX77pjKgbeibXviIt2oxEjTc8O2fqHX8mDdBrDvX8MYD/RYsBv4OiA==", - "dev": true, - "dependencies": { - "browserslist": "^4.17.0", - "semver": "7.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-js-pure": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.17.3.tgz", - "integrity": "sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "optional": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.1" - } - }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", - "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/css-vendor": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", - "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", - "dependencies": { - "is-in-browser": "^1.0.2" - } - }, - "node_modules/cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/csstype": { - "version": "2.6.18", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", - "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/dataloader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", - "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" - }, - "node_modules/date-fns": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.23.0.tgz", - "integrity": "sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==", - "dev": true, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" - }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "dependencies": { - "@babel/runtime": "^7.1.2" - } - }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-safe": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", - "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", - "dependencies": { - "dotenv": "^8.2.0" - } - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/electron-to-chromium": { - "version": "1.3.840", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz", - "integrity": "sha512-yRoUmTLDJnkIJx23xLY7GbSvnmDCq++NSuxHDQ0jiyDJ9YZBUGJcrdUqm+ZwZFzMbCciVzfem2N2AWiHJcWlbw==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", - "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.18.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", - "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-string": "^1.0.7", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-module-lexer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==", - "dev": true, - "peer": true - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peerDependencies": { - "eslint": "^7.12.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1 || ^5.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz", - "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "pkg-dir": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", - "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.6.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.4", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/eslint-plugin-import/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-jest": { - "version": "24.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.4.0.tgz", - "integrity": "sha512-8qnt/hgtZ94E9dA6viqfViKBfkJwFHXgJmTWlMGDgunw1XJEGqm3eiPjDsTanM3/u/3Az82nyQM9GX7PM/QGmg==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", - "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", - "dev": true, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "optional": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - }, - "node_modules/expect": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.0.tgz", - "integrity": "sha512-oOTbawMQv7AK1FZURbPTgGSzmhxkjFzoARSvDjOMnOpeWuYQx1tP6rXu9MIX5mrACmyCAM7fSNP8IJO2f1p0CQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-regex-util": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express-request-language": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/express-request-language/-/express-request-language-1.1.15.tgz", - "integrity": "sha512-KiLUdEZCcgwh8qfIvkCrhz1MMAFx/Xj4UcspN4zUxVdp+bp+yFvqUMmlyMHK2nC5JlQV7VK5uFOoS5LrArTL1A==", - "dependencies": { - "accept-language": "^3.0.4", - "bcp47": "^1.1.2" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "optional": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/fast-glob/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/fast-glob/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "dependencies": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - } - }, - "node_modules/fbjs/node_modules/core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", - "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js." - }, - "node_modules/fbjs/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fbjs/node_modules/isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "dependencies": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, - "node_modules/fbjs/node_modules/node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "dependencies": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-type": { - "version": "12.4.2", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", - "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "optional": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "optional": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/fuzzaldrin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", - "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dev": true, - "dependencies": { - "ini": "1.3.7" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "dependencies": { - "delegate": "^3.1.2" - } - }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "node_modules/graphql": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.3.tgz", - "integrity": "sha512-sM+jXaO5KinTui6lbK/7b7H/Knj9BpjGxZ+Ki35v7YbUJxxdBCUqNM0h3CRVU1ZF9t5lNiBzvBCSYPvIwxPOQA==", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/graphql-depth-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", - "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", - "dependencies": { - "arrify": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "graphql": "*" - } - }, - "node_modules/graphql-playground-html": { - "version": "1.6.29", - "resolved": "https://registry.npmjs.org/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", - "integrity": "sha512-fbF/zZKuw2sdfKp8gjTORJ/I9xBsqeEYRseWxBzuR15NHMptRTT9414IyRCs3ognZzUDr5MDJgx97SlLZCtQyA==", - "dependencies": { - "xss": "^1.0.6" - } - }, - "node_modules/graphql-playground-middleware-express": { - "version": "1.7.22", - "resolved": "https://registry.npmjs.org/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.22.tgz", - "integrity": "sha512-PJLiCxLmN6Dp+dHGyHU92m9y3hB/RAkcUBWcqYl2fiP+EbpDDgNfElrsVzW60MhJe+LTV1PFqiInH2d3KNvlCQ==", - "dependencies": { - "graphql-playground-html": "^1.6.29" - }, - "peerDependencies": { - "express": "^4.16.2" - } - }, - "node_modules/graphql-redis-subscriptions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.4.0.tgz", - "integrity": "sha512-McIJK9R8sVbjTlkkaFMRAG5dOF6CPEPO+8PcyHlKARd64d4h4bwl0q/DnR2RA494ZrfykEvFWAnwuIM45470Vw==", - "dependencies": { - "iterall": "^1.3.0" - }, - "optionalDependencies": { - "ioredis": "^4.17.3" - }, - "peerDependencies": { - "graphql-subscriptions": "^1.0.0" - } - }, - "node_modules/graphql-relay": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.9.0.tgz", - "integrity": "sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==", - "engines": { - "node": "^12.20.0 || ^14.15.0 || >= 15.9.0" - }, - "peerDependencies": { - "graphql": "^15.5.3" - } - }, - "node_modules/graphql-scalars": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.10.1.tgz", - "integrity": "sha512-/6vDkUogePfumSJaCRVrVT1vpEhvE0m36AHTPzhLC/tucWxVolOCj0EgGKVUMc0uehe93ByuRBj8rEDaPRXVxg==", - "dependencies": { - "tslib": "~2.3.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-subscriptions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz", - "integrity": "sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g==", - "dependencies": { - "iterall": "^1.3.0" - }, - "peerDependencies": { - "graphql": "^0.10.5 || ^0.11.3 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-tag": { - "version": "2.12.5", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.5.tgz", - "integrity": "sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ==", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/graphql-validation-complexity": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/graphql-validation-complexity/-/graphql-validation-complexity-0.4.2.tgz", - "integrity": "sha512-4tzmN/70a06c2JH5fvISkoLX6oBDpqK22cvr2comge3HZHtBLD3n5Sl6MnQYMVhQqKGlpZWcCgD00MnyKNzYYg==", - "dependencies": { - "warning": "^4.0.3" - }, - "peerDependencies": { - "graphql": ">=0.9.5" - } - }, - "node_modules/graphql-voyager": { - "version": "1.0.0-rc.31", - "resolved": "https://registry.npmjs.org/graphql-voyager/-/graphql-voyager-1.0.0-rc.31.tgz", - "integrity": "sha512-eCaFL8niR3DZo1LUcFV8txwR+MDVIQsrFPmFC7Zwab8UyH5x3SLhx3aDrt998RVJRmY5aFP4q79RY2F3QtGRqA==", - "dependencies": { - "@f/animate": "^1.0.1", - "@material-ui/core": "^3.9.3", - "classnames": "^2.2.6", - "clipboard": "^2.0.4", - "commonmark": "^0.29.0", - "lodash": "^4.17.10", - "prop-types": "^15.7.2", - "svg-pan-zoom": "^3.6.0", - "viz.js": "2.1.2" - }, - "peerDependencies": { - "graphql": ">=14.0.0", - "react": ">=15.4.2", - "react-dom": ">=15.4.2" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "optional": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "optional": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indefinite-observable": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz", - "integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==", - "dependencies": { - "symbol-observable": "1.2.0" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true - }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ioredis": { - "version": "4.27.9", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.9.tgz", - "integrity": "sha512-hAwrx9F+OQ0uIvaJefuS3UTqW+ByOLyLIV+j0EH8ClNVxvFyH9Vmb08hCL4yje6mDYT5zMquShhypkd50RRzkg==", - "dependencies": { - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.1", - "denque": "^1.1.0", - "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", - "lodash.isarguments": "^3.1.0", - "p-map": "^2.1.0", - "redis-commands": "1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.1.1" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" - }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" - }, - "node_modules/jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.2.0.tgz", - "integrity": "sha512-oUqVXyvh5YwEWl263KWdPUAqEzBFzGHdFLQ05hUnITr1tH+9SscEI9A/GH9eBClA+Nw1ct+KNuuOV6wlnmBPcg==", - "dev": true, - "dependencies": { - "@jest/core": "^27.2.0", - "import-local": "^3.0.2", - "jest-cli": "^27.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.1.1.tgz", - "integrity": "sha512-5TV9+fYlC2A6hu3qtoyGHprBwCAn0AuGA77bZdUgYvVlRMjHXo063VcWTEAyx6XAZ85DYHqp0+aHKbPlfRDRvA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.0.tgz", - "integrity": "sha512-WwENhaZwOARB1nmcboYPSv/PwHBUGRpA4MEgszjr9DLCl97MYw0qZprBwLb7rNzvMwfIvNGG7pefQ5rxyBlzIA==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.2.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.2.0", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "pretty-format": "^27.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.0.tgz", - "integrity": "sha512-bq1X/B/b1kT9y1zIFMEW3GFRX1HEhFybiqKdbxM+j11XMMYSbU9WezfyWIhrSOmPT+iODLATVjfsCnbQs7cfIA==", - "dev": true, - "dependencies": { - "@jest/core": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.2.0.tgz", - "integrity": "sha512-Z1romHpxeNwLxQtouQ4xt07bY6HSFGKTo0xJcvOK3u6uJHveA4LB2P+ty9ArBLpTh3AqqPxsyw9l9GMnWBYS9A==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.2.0", - "@jest/types": "^27.1.1", - "babel-jest": "^27.2.0", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.2.0", - "jest-environment-jsdom": "^27.2.0", - "jest-environment-node": "^27.2.0", - "jest-get-type": "^27.0.6", - "jest-jasmine2": "^27.2.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.2.0", - "jest-runner": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "micromatch": "^4.0.4", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jest-config/node_modules/jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/jest-diff": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.0.tgz", - "integrity": "sha512-QSO9WC6btFYWtRJ3Hac0sRrkspf7B01mGrrQEiCW6TobtViJ9RWL0EmOs/WnBsZDsI/Y2IoSHZA2x6offu0sYw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.2.0.tgz", - "integrity": "sha512-biDmmUQjg+HZOB7MfY2RHSFL3j418nMoC3TK3pGAj880fQQSxvQe1y2Wy23JJJNUlk6YXiGU0yWy86Le1HBPmA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "jest-util": "^27.2.0", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.0.tgz", - "integrity": "sha512-wNQJi6Rd/AkUWqTc4gWhuTIFPo7tlMK0RPZXeM6AqRHZA3D3vwvTa9ktAktyVyWYmUoXdYstOfyYMG3w4jt7eA==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.2.0", - "@jest/fake-timers": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "jest-mock": "^27.1.1", - "jest-util": "^27.2.0", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.0.tgz", - "integrity": "sha512-WbW+vdM4u88iy6Q3ftUEQOSgMPtSgjm3qixYYK2AKEuqmFO2zmACTw1vFUB0qI/QN88X6hA6ZkVKIdIWWzz+yg==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.2.0", - "@jest/fake-timers": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "jest-mock": "^27.1.1", - "jest-util": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-fetch-mock": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", - "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", - "dev": true, - "dependencies": { - "cross-fetch": "^3.0.4", - "promise-polyfill": "^8.1.3" - } - }, - "node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.0.tgz", - "integrity": "sha512-laFet7QkNlWjwZtMGHCucLvF8o9PAh2cgePRck1+uadSM4E4XH9J4gnx4do+a6do8ZV5XHNEAXEkIoNg5XUH2Q==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/jest-haste-map/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jest-haste-map/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/jest-haste-map/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.0.tgz", - "integrity": "sha512-NcPzZBk6IkDW3Z2V8orGueheGJJYfT5P0zI/vTO/Jp+R9KluUdgFrgwfvZ0A34Kw6HKgiWFILZmh3oQ/eS+UxA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.2.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.2.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.2.0", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "pretty-format": "^27.2.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.0.tgz", - "integrity": "sha512-e91BIEmbZw5+MHkB4Hnrq7S86coTxUMCkz4n7DLmQYvl9pEKmRx9H/JFH87bBqbIU5B2Ju1soKxRWX6/eGFGpA==", - "dev": true, - "dependencies": { - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.0.tgz", - "integrity": "sha512-F+LG3iTwJ0gPjxBX6HCyrARFXq6jjiqhwBQeskkJQgSLeF1j6ui1RTV08SR7O51XTUhtc8zqpDj8iCG4RGmdKw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.2.0", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.0.tgz", - "integrity": "sha512-y+sfT/94CiP8rKXgwCOzO1mUazIEdEhrLjuiu+RKmCP+8O/TJTSne9dqQRbFIHBtlR2+q7cddJlWGir8UATu5w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.1.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jest-message-util/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/jest-mock": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.1.1.tgz", - "integrity": "sha512-SClsFKuYBf+6SSi8jtAYOuPw8DDMsTElUWEae3zq7vDhH01ayVSIHUSIa8UgbDOUalCFp6gNsaikN0rbxN4dbw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.0.tgz", - "integrity": "sha512-v09p9Ib/VtpHM6Cz+i9lEAv1Z/M5NVxsyghRHRMEUOqwPQs3zwTdwp1xS3O/k5LocjKiGS0OTaJoBSpjbM2Jlw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "resolve": "^1.20.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.0.tgz", - "integrity": "sha512-EY5jc/Y0oxn+oVEEldTidmmdVoZaknKPyDORA012JUdqPyqPL+lNdRyI3pGti0RCydds6coaw6xt4JQY54dKsg==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.0.tgz", - "integrity": "sha512-Cl+BHpduIc0cIVTjwoyx0pQk4Br8gn+wkr35PmKCmzEdOUnQ2wN7QVXA8vXnMQXSlFkN/+KWnk20TAVBmhgrww==", - "dev": true, - "dependencies": { - "@jest/console": "^27.2.0", - "@jest/environment": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.2.0", - "jest-environment-node": "^27.2.0", - "jest-haste-map": "^27.2.0", - "jest-leak-detector": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-resolve": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.0.tgz", - "integrity": "sha512-6gRE9AVVX49hgBbWQ9PcNDeM4upMUXzTpBs0kmbrjyotyUyIJixLPsYjpeTFwAA07PVLDei1iAm2chmWycdGdQ==", - "dev": true, - "dependencies": { - "@jest/console": "^27.2.0", - "@jest/environment": "^27.2.0", - "@jest/fake-timers": "^27.2.0", - "@jest/globals": "^27.2.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-mock": "^27.1.1", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.0.tgz", - "integrity": "sha512-MukJvy3KEqemCT2FoT3Gum37CQqso/62PKTfIzWmZVTsLsuyxQmJd2PI5KPcBYFqLlA8LgZLHM8ZlazkVt8LsQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.2.0", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.2.0", - "jest-get-type": "^27.0.6", - "jest-haste-map": "^27.2.0", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-resolve": "^27.2.0", - "jest-util": "^27.2.0", - "natural-compare": "^1.4.0", - "pretty-format": "^27.2.0", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.2.0.tgz", - "integrity": "sha512-T5ZJCNeFpqcLBpx+Hl9r9KoxBCUqeWlJ1Htli+vryigZVJ1vuLB9j35grEBASp4R13KFkV7jM52bBGnArpJN6A==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.0.tgz", - "integrity": "sha512-SjRWhnr+qO8aBsrcnYIyF+qRxNZk6MZH8TIDgvi+VlsyrvOyqg0d+Rm/v9KHiTtC9mGGeFi9BFqgavyWib6xLg==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.2.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.0.tgz", - "integrity": "sha512-laB0ZVIBz+voh/QQy9dmUuuDsadixeerrKqyVpgPz+CCWiOYjOBabUXHIXZhsdvkWbLqSHbgkAHWl5cg24Q6RA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/jss": { - "version": "9.8.7", - "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", - "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", - "hasInstallScript": true, - "dependencies": { - "is-in-browser": "^1.1.3", - "symbol-observable": "^1.1.0", - "warning": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jss-camel-case": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz", - "integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==", - "dependencies": { - "hyphenate-style-name": "^1.0.2" - }, - "peerDependencies": { - "jss": "^9.7.0" - } - }, - "node_modules/jss-default-unit": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", - "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==", - "peerDependencies": { - "jss": "^9.4.0" - } - }, - "node_modules/jss-global": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", - "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==", - "peerDependencies": { - "jss": "^9.0.0" - } - }, - "node_modules/jss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz", - "integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==", - "dependencies": { - "warning": "^3.0.0" - }, - "peerDependencies": { - "jss": "^9.0.0" - } - }, - "node_modules/jss-nested/node_modules/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/jss-props-sort": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", - "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==", - "peerDependencies": { - "jss": "^9.0.0" - } - }, - "node_modules/jss-vendor-prefixer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz", - "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==", - "dependencies": { - "css-vendor": "^0.3.8" - }, - "peerDependencies": { - "jss": "^9.0.0" - } - }, - "node_modules/jss/node_modules/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/make-plural": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.2.tgz", - "integrity": "sha512-8iTuFioatnTTmb/YJjywkVIHLjcwkFD9Ms0JpxjEm9Mo8eQYkh1z+55dwv4yc1jQ8ftVBxWQbihvZL1DfzGGWA==" - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "optional": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/messageformat-parser": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", - "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-kind": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", - "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", - "dependencies": { - "file-type": "^12.1.0", - "mime-types": "^2.1.24" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dependencies": { - "mime-db": "1.49.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "optional": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multi-part": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", - "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", - "dependencies": { - "mime-kind": "^3.0.0", - "multi-part-lite": "^1.0.0" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/multi-part-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", - "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==", - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/node-environment-flags/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-gettext": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", - "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", - "dev": true, - "dependencies": { - "lodash.get": "^4.4.2" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", - "dev": true - }, - "node_modules/nodemon": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", - "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-scroll-left": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz", - "integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg==" - }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/notifications-node-client": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-5.1.0.tgz", - "integrity": "sha512-a3aoSZPHSc/8VaccfGvKKsIZ/crqbglP9dNvg0pHHTgWi6BYiJc+Md7wOPizzEPACa+SKdifs06VY8ktbTzySA==", - "dependencies": { - "axios": "^0.21.1", - "jsonwebtoken": "^8.2.1", - "underscore": "^1.9.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "optional": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "optional": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "optional": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/papaparse": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", - "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-headers": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", - "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/plurals-cldr": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/plurals-cldr/-/plurals-cldr-1.0.4.tgz", - "integrity": "sha512-4nLXqtel7fsCgzi8dvRZvUjfL8SXpP982sKg7b2TgpnR8rDnes06iuQ83trQ/+XdtyMIQkBBbKzX6x97eLfsJQ==", - "dev": true - }, - "node_modules/pofile": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.1.tgz", - "integrity": "sha512-RVAzFGo1Mx9+YukVKSgTLut6r4ZVBW8IVrqGHAPfEsVJN93WSp5HRD6+qNa7av1q/joPKDNJd55m5AJl9GBQGA==", - "dev": true - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/pretty-format": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz", - "integrity": "sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.1.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "optional": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dependencies": { - "asap": "~2.0.3" - } - }, - "node_modules/promise-polyfill": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz", - "integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==", - "dev": true - }, - "node_modules/prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pseudolocale": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.2.0.tgz", - "integrity": "sha512-k0OQFvIlvpRdzR0dPVrrbWX7eE9EaZ6gpZtTlFSDi1Gf9tMy9wiANCNu7JZ0drcKgUri/39a2mBbH0goiQmrmQ==", - "dev": true, - "dependencies": { - "commander": "*" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/react-event-listener": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", - "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": "^16.3.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "node_modules/react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "dependencies": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/readdirp/node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/recompose": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", - "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "change-emitter": "^0.1.2", - "fbjs": "^0.8.1", - "hoist-non-react-statics": "^2.3.1", - "react-lifecycles-compat": "^3.0.2", - "symbol-observable": "^1.0.4" - }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/recompose/node_modules/hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "node_modules/redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "optional": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true, - "optional": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true, - "optional": true - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "optional": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "optional": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "optional": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "optional": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "optional": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true, - "optional": true - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "optional": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "node_modules/stack-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.4.tgz", - "integrity": "sha512-ERg+H//lSSYlZhBIUu+wJnqg30AbyBbpZlIhcshpn7BNzpoRODZgfyr9J+8ERf3ooC6af3u7Lcl01nleau7MrA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0", - "source-map-support": "^0.5.20" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "optional": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.repeat": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", - "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", - "dependencies": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependencies": { - "graphql": ">=0.10.0" - } - }, - "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dev": true, - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/node_modules/qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/superagent/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supertest": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.6.tgz", - "integrity": "sha512-0hACYGNJ8OHRg8CRITeZOdbjur7NLuNs0mBjVhdpxi7hP6t3QIbOzLON5RTUmZcy2I9riuII3+Pr2C7yztrIIg==", - "dev": true, - "dependencies": { - "methods": "^1.1.2", - "superagent": "^6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/svg-pan-zoom": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/svg-pan-zoom/-/svg-pan-zoom-3.6.1.tgz", - "integrity": "sha512-JaKkGHHfGvRrcMPdJWkssLBeWqM+Isg/a09H7kgNNajT1cX5AztDTNs+C8UzpCxjCTRrG34WbquwaovZbmSk9g==" - }, - "node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", - "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", - "dev": true, - "peer": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", - "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, - "peer": true, - "dependencies": { - "jest-worker": "^27.0.6", - "p-limit": "^3.1.0", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "optional": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "optional": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", - "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "dependencies": { - "debug": "^2.2.0" - } - }, - "node_modules/undefsafe/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/undefsafe/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "optional": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "optional": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "optional": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "optional": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dev": true, - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/update-notifier/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/update-notifier/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true, - "optional": true - }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/url-slug": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/url-slug/-/url-slug-3.0.2.tgz", - "integrity": "sha512-puioWUGY+esk4kKW8L6fCZWb+xK1+0L/KH2miV6GEJdlCJRJ2lfRlvHkUikyEU1e1v4j1C1HBQKvuljFOxmnEA==" - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", - "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validator": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", - "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/value-or-promise": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.10.tgz", - "integrity": "sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/viz.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-2.1.2.tgz", - "integrity": "sha512-UO6CPAuEMJ8oNR0gLLNl+wUiIzQUsyUOp8SyyDKTqVRBtq7kk1VnFmIZW8QufjxGrGEuI+LVR7p/C7uEKy0LQw==", - "deprecated": "no longer supported" - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", - "dev": true, - "peer": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "5.52.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz", - "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.0", - "es-module-lexer": "^0.7.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.1.tgz", - "integrity": "sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", - "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", - "dev": true, - "peer": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/x3-linkedlist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", - "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==", - "engines": { - "node": ">=10" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", - "dependencies": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xss": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz", - "integrity": "sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ==", - "dependencies": { - "commander": "^2.20.3", - "cssfilter": "0.0.10" - }, - "bin": { - "xss": "bin/xss" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/xss/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@apollo/protobufjs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", - "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", - "requires": { - "@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/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" - } - } - }, - "@apollographql/apollo-tools": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.1.tgz", - "integrity": "sha512-ZII+/xUFfb9ezDU2gad114+zScxVFMVlZ91f8fGApMzlS1kkqoyLnC4AJaQ1Ya/X+b63I20B4Gd+eCL8QuB4sA==" - }, - "@apollographql/graphql-playground-html": { - "version": "1.6.29", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", - "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", - "requires": { - "xss": "^1.0.8" - } - }, - "@babel/cli": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.15.4.tgz", - "integrity": "sha512-9RhhQ7tgKRcSO/jI3rNLxalLSk30cHqeM8bb+nGOJTyYBDpkoXw/A9QHZ2SYjlslAt4tr90pZQGIEobwWHSIDw==", - "dev": true, - "requires": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.2", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - } - }, - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true - }, - "@babel/core": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", - "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.5", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz", - "integrity": "sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.15.4.tgz", - "integrity": "sha512-P8o7JP2Mzi0SdC6eWr1zF+AEYvrsZa7GSY1lTayjF5XJhVH0kjLYUZPvTMflP7tBgZoe9gIhTa60QwFpqh/E0Q==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.4.tgz", - "integrity": "sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", - "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.15.4.tgz", - "integrity": "sha512-J14f/vq8+hdC2KoWLIQSsGrC9EFBKE4NFts8pfMpymfApds+fPqR30AOUWc4tyr56h9l/GA1Sxv2q3dLZWbQ/g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.4.tgz", - "integrity": "sha512-9fHHSGE9zTC++KuXLZcB5FKgvlV83Ox+NLUmQTawovwlJ85+QMhk1CnVk406CQVj97LaWod6KVjl2Sfgw9Aktw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.14.9", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz", - "integrity": "sha512-v53MxgvMK/HCwckJ1bZrq6dNKlmwlyRNYM6ypaRTdXWGOE2c1/SCa6dL/HimhPulGhZKw9W0QhREM583F/t0vQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-wrap-function": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz", - "integrity": "sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.15.4.tgz", - "integrity": "sha512-Y2o+H/hRV5W8QhIfTpRIBwl57y8PrZt6JM3V8FOo5qarjshHItyH5lXlpMfBfmBefOqSCpKZs/6Dxqp0E/U+uw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dev": true, - "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/node": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.15.4.tgz", - "integrity": "sha512-UZue+j8p5aKTaVjvy5psYmqLHqmz+9cIboAFoa97S1xeZyUr0gT6KzXB8ZkfBIsP/u79biOdjGHVXBXnW3rVfw==", - "dev": true, - "requires": { - "@babel/register": "^7.15.3", - "commander": "^4.0.1", - "core-js": "^3.16.0", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.13.4", - "v8flags": "^3.1.1" - } - }, - "@babel/parser": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.6.tgz", - "integrity": "sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==", - "dev": true - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz", - "integrity": "sha512-eBnpsl9tlhPhpI10kU06JHnrYXwg3+V6CaP2idsCXNef0aeslpqyITXQ74Vfk5uHgY7IG7XP0yIH8b42KSzHog==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4", - "@babel/plugin-proposal-optional-chaining": "^7.14.5" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.4.tgz", - "integrity": "sha512-2zt2g5vTXpMC3OmK6uyjvdXptbhBXfA77XGrd3gh93zwG8lZYBLOBImiGBEG0RANu3JqKEACCz5CGk73OJROBw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.15.4", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.15.4.tgz", - "integrity": "sha512-M682XWrrLNk3chXCjoPUQWOyYsB93B9z3mRyjtqqYJWDf2mfCdIYgDrA11cgNVhAQieaq6F2fn2f3wI0U4aTjA==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", - "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", - "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", - "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", - "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", - "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", - "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.15.6.tgz", - "integrity": "sha512-qtOHo7A1Vt+O23qEAX+GdBpqaIuD3i9VRrWgCJeq7WO6H2d14EK3q11urj5Te2MAeK97nMiIdRpwd/ST4JFbNg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.15.4" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", - "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", - "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", - "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.15.4.tgz", - "integrity": "sha512-X0UTixkLf0PCCffxgu5/1RQyGGbgZuKoI+vXP4iSbJSYwPb7hu06omsFGBvQ9lJEvwgrxHdS8B5nbfcd8GyUNA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", - "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", - "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", - "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.15.4.tgz", - "integrity": "sha512-Yjvhex8GzBmmPQUvpXRPWQ9WnxXgAFuZSrqOK/eJlOGIXwvv8H3UEdUigl1gb/bnjTrln+e8bkZUYCBt/xYlBg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", - "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", - "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", - "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", - "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.15.4.tgz", - "integrity": "sha512-DRTY9fA751AFBDh2oxydvVm4SYevs5ILTWLs6xKXps4Re/KG5nfUkr+TdHCrRWB8C69TlzVgA9b3RmGWmgN9LA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", - "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", - "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", - "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", - "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.4.tgz", - "integrity": "sha512-qg4DPhwG8hKp4BbVDvX1s8cohM8a6Bvptu4l6Iingq5rW+yRUAhe/YRup/YcW2zCOlrysEWVhftIcKzrEZv3sA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.15.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.15.4.tgz", - "integrity": "sha512-fJUnlQrl/mezMneR72CKCgtOoahqGJNVKpompKwzv3BrEXdlPspTcyxrZ1XmDTIr9PpULrgEQo3qNKp6dW7ssw==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", - "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", - "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", - "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", - "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", - "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", - "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", - "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", - "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", - "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", - "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", - "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", - "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", - "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", - "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", - "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/preset-env": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.15.6.tgz", - "integrity": "sha512-L+6jcGn7EWu7zqaO2uoTDjjMBW+88FXzV8KvrBl2z6MtRNxlsmUNRlZPaNNPUTgqhyC5DHNFk/2Jmra+ublZWw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.15.4", - "@babel/plugin-proposal-async-generator-functions": "^7.15.4", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-class-static-block": "^7.15.4", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-json-strings": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-object-rest-spread": "^7.15.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.15.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@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/plugin-transform-arrow-functions": "^7.14.5", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/plugin-transform-block-scoped-functions": "^7.14.5", - "@babel/plugin-transform-block-scoping": "^7.15.3", - "@babel/plugin-transform-classes": "^7.15.4", - "@babel/plugin-transform-computed-properties": "^7.14.5", - "@babel/plugin-transform-destructuring": "^7.14.7", - "@babel/plugin-transform-dotall-regex": "^7.14.5", - "@babel/plugin-transform-duplicate-keys": "^7.14.5", - "@babel/plugin-transform-exponentiation-operator": "^7.14.5", - "@babel/plugin-transform-for-of": "^7.15.4", - "@babel/plugin-transform-function-name": "^7.14.5", - "@babel/plugin-transform-literals": "^7.14.5", - "@babel/plugin-transform-member-expression-literals": "^7.14.5", - "@babel/plugin-transform-modules-amd": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.15.4", - "@babel/plugin-transform-modules-systemjs": "^7.15.4", - "@babel/plugin-transform-modules-umd": "^7.14.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9", - "@babel/plugin-transform-new-target": "^7.14.5", - "@babel/plugin-transform-object-super": "^7.14.5", - "@babel/plugin-transform-parameters": "^7.15.4", - "@babel/plugin-transform-property-literals": "^7.14.5", - "@babel/plugin-transform-regenerator": "^7.14.5", - "@babel/plugin-transform-reserved-words": "^7.14.5", - "@babel/plugin-transform-shorthand-properties": "^7.14.5", - "@babel/plugin-transform-spread": "^7.14.6", - "@babel/plugin-transform-sticky-regex": "^7.14.5", - "@babel/plugin-transform-template-literals": "^7.14.5", - "@babel/plugin-transform-typeof-symbol": "^7.14.5", - "@babel/plugin-transform-unicode-escapes": "^7.14.5", - "@babel/plugin-transform-unicode-regex": "^7.14.5", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.15.6", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.2", - "babel-plugin-polyfill-regenerator": "^0.2.2", - "core-js-compat": "^3.16.0", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/register": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.15.3.tgz", - "integrity": "sha512-mj4IY1ZJkorClxKTImccn4T81+UKTo4Ux0+OFSV9hME1ooqS9UV+pJ6BjD0qXPK4T3XW/KNa79XByjeEMZz+fw==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.0", - "source-map-support": "^0.5.16" - } - }, - "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", - "dev": true, - "requires": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@f/animate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@f/animate/-/animate-1.0.1.tgz", - "integrity": "sha1-oDE5itrfmgvTpWOYzskx+HfYhIU=", - "requires": { - "@f/elapsed-time": "^1.0.0", - "@f/raf": "^1.0.0", - "@f/tween": "^1.0.0" - } - }, - "@f/elapsed-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@f/elapsed-time/-/elapsed-time-1.0.0.tgz", - "integrity": "sha1-ageaYQSocni/W0CARE7wLRtZVEk=", - "requires": { - "@f/timestamp": "^1.0.0" - } - }, - "@f/map-obj": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@f/map-obj/-/map-obj-1.2.2.tgz", - "integrity": "sha1-2an4vXbKoq4RtjPdok2cbMzB5g0=" - }, - "@f/raf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@f/raf/-/raf-1.0.3.tgz", - "integrity": "sha1-Mt3KN940WyDIw4QwGMxRuPiXkU0=" - }, - "@f/timestamp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@f/timestamp/-/timestamp-1.0.0.tgz", - "integrity": "sha1-MqkWbiUW5cy5sPz9yJIjgZcQ6Iw=" - }, - "@f/tween": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@f/tween/-/tween-1.0.1.tgz", - "integrity": "sha1-GK73nEl15UQVrfMm5LXg0FPSB/A=", - "requires": { - "@f/map-obj": "^1.2.2" - } - }, - "@graphql-tools/merge": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.1.2.tgz", - "integrity": "sha512-kFLd4kKNJXYXnKIhM8q9zgGAtbLmsy3WmGdDxYq3YHBJUogucAxnivQYyRIseUq37KGmSAIWu3pBQ23TKGsGOw==", - "requires": { - "@graphql-tools/utils": "^8.2.2", - "tslib": "~2.3.0" - } - }, - "@graphql-tools/mock": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.3.1.tgz", - "integrity": "sha512-iJ3GeQ10Vqa0Tg4QJHiulxUUI4r84RAvltM3Sc+XPj07QlrLzMHOHO/goO7FC4TN2/HVncj7pWHwrmLPT9du/Q==", - "requires": { - "@graphql-tools/schema": "^8.2.0", - "@graphql-tools/utils": "^8.2.0", - "fast-json-stable-stringify": "^2.1.0", - "tslib": "~2.3.0" - } - }, - "@graphql-tools/schema": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.2.0.tgz", - "integrity": "sha512-ufmI5mJQa8NJczzfkh0pUttKvspqDcT5LLakA3jUmOrrE4d4NVj6onZlazdTzF5sAepSNqanFnwhrxZpCAJMKg==", - "requires": { - "@graphql-tools/merge": "^8.1.0", - "@graphql-tools/utils": "^8.2.0", - "tslib": "~2.3.0", - "value-or-promise": "1.0.10" - } - }, - "@graphql-tools/utils": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.2.2.tgz", - "integrity": "sha512-29FFY5U4lpXuBiW9dRvuWnBVwGhWbGLa2leZcAMU/Pz47Cr/QLZGVgpLBV9rt+Gbs7wyIJM7t7EuksPs0RDm3g==", - "requires": { - "tslib": "~2.3.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "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": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.2.0.tgz", - "integrity": "sha512-35z+RqsK2CCgNxn+lWyK8X4KkaDtfL4BggT7oeZ0JffIiAiEYFYPo5B67V50ZubqDS1ehBrdCR2jduFnIrZOYw==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.2.0", - "jest-util": "^27.2.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.2.0.tgz", - "integrity": "sha512-E/2NHhq+VMo18DpKkoty8Sjey8Kps5Cqa88A8NP757s6JjYqPdioMuyUBhDiIOGCdQByEp0ou3jskkTszMS0nw==", - "dev": true, - "requires": { - "@jest/console": "^27.2.0", - "@jest/reporters": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.1.1", - "jest-config": "^27.2.0", - "jest-haste-map": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.2.0", - "jest-resolve-dependencies": "^27.2.0", - "jest-runner": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "jest-watcher": "^27.2.0", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - } - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "@jest/environment": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.2.0.tgz", - "integrity": "sha512-iPWmQI0wRIYSZX3wKu4FXHK4eIqkfq6n1DCDJS+v3uby7SOXrHvX4eiTBuEdSvtDRMTIH2kjrSkjHf/F9JIYyQ==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "jest-mock": "^27.1.1" - } - }, - "@jest/fake-timers": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.0.tgz", - "integrity": "sha512-gSu3YHvQOoVaTWYGgHFB7IYFtcF2HBzX4l7s47VcjvkUgL4/FBnE20x7TNLa3W6ABERtGd5gStSwsA8bcn+c4w==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.2.0", - "jest-mock": "^27.1.1", - "jest-util": "^27.2.0" - } - }, - "@jest/globals": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.2.0.tgz", - "integrity": "sha512-raqk9Gf9WC3hlBa57rmRmJfRl9hom2b+qEE/ifheMtwn5USH5VZxzrHHOZg0Zsd/qC2WJ8UtyTwHKQAnNlDMdg==", - "dev": true, - "requires": { - "@jest/environment": "^27.2.0", - "@jest/types": "^27.1.1", - "expect": "^27.2.0" - } - }, - "@jest/reporters": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.0.tgz", - "integrity": "sha512-7wfkE3iRTLaT0F51h1mnxH3nQVwDCdbfgXiLuCcNkF1FnxXLH9utHqkSLIiwOTV1AtmiE0YagHbOvx4rnMP/GA==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.2.0", - "jest-resolve": "^27.2.0", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "@jest/test-result": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.0.tgz", - "integrity": "sha512-JPPqn8h0RGr4HyeY1Km+FivDIjTFzDROU46iAvzVjD42ooGwYoqYO/MQTilhfajdz6jpVnnphFrKZI5OYrBONA==", - "dev": true, - "requires": { - "@jest/console": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.0.tgz", - "integrity": "sha512-PrqarcpzOU1KSAK7aPwfL8nnpaqTMwPe7JBPnaOYRDSe/C6AoJiL5Kbnonqf1+DregxZIRAoDg69R9/DXMGqXA==", - "dev": true, - "requires": { - "@jest/test-result": "^27.2.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-runtime": "^27.2.0" - } - }, - "@jest/transform": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.2.0.tgz", - "integrity": "sha512-Q8Q/8xXIZYllk1AF7Ou5sV3egOZsdY/Wlv09CSbcexBRcC1Qt6lVZ7jRFAZtbHsEEzvOCyFEC4PcrwKwyjXtCg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.1.1", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.2.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "@jest/types": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", - "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@josephg/resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", - "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" - }, - "@lingui/babel-plugin-extract-messages": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.11.1.tgz", - "integrity": "sha512-MAsZ0BYIsHh08dptT7bA6Jsh1ixO1sBU8eNDtobkZaZ78SXIUMUYCy9e3T9D/RYpecgDGaFUf2djctTqguMgmQ==", - "dev": true, - "requires": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.11.1", - "mkdirp": "^1.0.4" - } - }, - "@lingui/cli": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.11.1.tgz", - "integrity": "sha512-piKjrGjiornzX18Lt6EhyICAHEGH9wio0KaOXKyCjHqPw8sQnC4AZv0iyZqTACVYL+0ROsrtNd/xgDMYNSQgeA==", - "dev": true, - "requires": { - "@babel/generator": "^7.11.6", - "@babel/parser": "^7.11.5", - "@babel/plugin-syntax-jsx": "^7.10.4", - "@babel/runtime": "^7.11.2", - "@babel/types": "^7.11.5", - "@lingui/babel-plugin-extract-messages": "^3.11.1", - "@lingui/conf": "^3.11.1", - "babel-plugin-macros": "^3.0.1", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", - "chokidar": "3.5.1", - "cli-table": "^0.3.1", - "commander": "^6.1.0", - "date-fns": "^2.16.1", - "fs-extra": "^9.0.1", - "fuzzaldrin": "^2.1.0", - "glob": "^7.1.4", - "inquirer": "^7.3.3", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3", - "micromatch": "4.0.2", - "mkdirp": "^1.0.4", - "node-gettext": "^3.0.0", - "normalize-path": "^3.0.0", - "ora": "^5.1.0", - "papaparse": "^5.3.0", - "pkg-up": "^3.1.0", - "plurals-cldr": "^1.0.4", - "pofile": "^1.1.0", - "pseudolocale": "^1.1.0", - "ramda": "^0.27.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "@lingui/conf": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.11.1.tgz", - "integrity": "sha512-WoEdtDAiI+TR7Gz2F7VMZQyIGZFP2b4qT3JO3gLuGzHY6a6DCqOMojqUuo6KHFQrUoUtebI/1Yn7gAxVH1xcWQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "jest-validate": "^26.5.2", - "lodash.get": "^4.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@lingui/core": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.11.1.tgz", - "integrity": "sha512-qHMo47SbwFFx3IwXbMRafIMZH2tTYebrQhvAu5wH9OabI+bqbVWbTOLSAXzX/gDZqxMQWIrr2ndrah0aPZk6EQ==", - "requires": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" - } - }, - "@lingui/loader": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.11.1.tgz", - "integrity": "sha512-utBvBfbC/mupSShiEpUWBdTP7hC35iBJ3gji88NpY8MSr/6HZoaIw/eEv3n+jyygXYeDIHh2+xZjneOJ++xTsg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.11.1", - "@lingui/conf": "^3.11.1", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" - } - }, - "@lingui/macro": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.11.1.tgz", - "integrity": "sha512-rSzvBs4Gasn6VO8msYA0/Bw285jUOBoLAVxURt6XaH45NXnJiWnDtEOD/DhQcuggDKbaWwz13lXOiRfUP0zG6g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.11.1", - "ramda": "^0.27.1" - } - }, - "@material-ui/core": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.4.tgz", - "integrity": "sha512-r8QFLSexcYZbnqy/Hn4v8xzmAJV41yaodUVjmbGLi1iGDLG3+W941hEtEiBmxTRRqv2BdK3r4ijILcqKmDv/Sw==", - "requires": { - "@babel/runtime": "^7.2.0", - "@material-ui/system": "^3.0.0-alpha.0", - "@material-ui/utils": "^3.0.0-alpha.2", - "@types/jss": "^9.5.6", - "@types/react-transition-group": "^2.0.8", - "brcast": "^3.0.1", - "classnames": "^2.2.5", - "csstype": "^2.5.2", - "debounce": "^1.1.0", - "deepmerge": "^3.0.0", - "dom-helpers": "^3.2.1", - "hoist-non-react-statics": "^3.2.1", - "is-plain-object": "^2.0.4", - "jss": "^9.8.7", - "jss-camel-case": "^6.0.0", - "jss-default-unit": "^8.0.2", - "jss-global": "^3.0.0", - "jss-nested": "^6.0.1", - "jss-props-sort": "^6.0.0", - "jss-vendor-prefixer": "^7.0.0", - "normalize-scroll-left": "^0.1.2", - "popper.js": "^1.14.1", - "prop-types": "^15.6.0", - "react-event-listener": "^0.6.2", - "react-transition-group": "^2.2.1", - "recompose": "0.28.0 - 0.30.0", - "warning": "^4.0.1" - }, - "dependencies": { - "deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" - } - } - }, - "@material-ui/system": { - "version": "3.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", - "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", - "requires": { - "@babel/runtime": "^7.2.0", - "deepmerge": "^3.0.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - }, - "dependencies": { - "deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" - } - } - }, - "@material-ui/utils": { - "version": "3.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", - "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", - "requires": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "react-is": "^16.6.3" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.2", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz", - "integrity": "sha512-Fb8WxUFOBQVl+CX4MWet5o7eCc6Pj04rXIwVKZ6h1NnqTo45eOQW6aWyhG25NIODvWFwTDMwBsYxrQ3imxpetg==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^5.1.2", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" - }, - "@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", - "dev": true, - "peer": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true, - "peer": true - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/jss": { - "version": "9.5.8", - "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz", - "integrity": "sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA==", - "requires": { - "csstype": "^2.0.0", - "indefinite-observable": "^1.0.1" - } - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "@types/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/react": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.21.tgz", - "integrity": "sha512-GzzXCpOthOjXvrAUFQwU/svyxu658cwu00Q9ugujS4qc1zXgLFaO0kS2SLOaMWLt2Jik781yuHCWB7UcYdGAeQ==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - }, - "dependencies": { - "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - } - } - }, - "@types/react-transition-group": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", - "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "@typescript-eslint/experimental-utils": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz", - "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.31.1", - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/typescript-estree": "4.31.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - } - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz", - "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/visitor-keys": "4.31.1" - } - }, - "@typescript-eslint/types": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", - "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", - "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/visitor-keys": "4.31.1", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz", - "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.31.1", - "eslint-visitor-keys": "^2.0.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accept-language": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", - "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=", - "requires": { - "bcp47": "^1.1.2", - "stable": "^0.1.6" - } - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peer": true, - "requires": {} - }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "apollo-datasource": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.1.0.tgz", - "integrity": "sha512-ywcVjuWNo84eMB9uBOYygQI+00+Ne4ShyPIxJzT//sn1j1Fu3J+KStMNd6s1jyERWgjGZzxkiLn6nLmwsGymBg==", - "requires": { - "apollo-server-caching": "^3.1.0", - "apollo-server-env": "^4.0.3" - } - }, - "apollo-graphql": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.3.tgz", - "integrity": "sha512-rcAl2E841Iko4kSzj4Pt3PRBitmyq1MvoEmpl04TQSpGnoVgl1E/ZXuLBYxMTSnEAm7umn2IsoY+c6Ll9U/10A==", - "requires": { - "core-js-pure": "^3.10.2", - "lodash.sortby": "^4.7.0", - "sha.js": "^2.4.11" - } - }, - "apollo-reporting-protobuf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.0.0.tgz", - "integrity": "sha512-jmCD+6gECt8KS7PxP460hztT/5URTbv2Kg0zgnR6iWPGce88IBmSUjcqf1Z6wJJq7Teb8Hu7WbyyMhn0vN5TxQ==", - "requires": { - "@apollo/protobufjs": "1.2.2" - } - }, - "apollo-server": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.3.0.tgz", - "integrity": "sha512-vJE+lIOQMgno3IaJvwo66S53m0CU4fq7qAgnOCubitIGNKEHiUL6yXKL0rMGfOPZqeed2S2QWQrOsCGQDIudMw==", - "requires": { - "apollo-server-core": "^3.3.0", - "apollo-server-express": "^3.3.0", - "express": "^4.17.1" - } - }, - "apollo-server-caching": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.1.0.tgz", - "integrity": "sha512-bZ4bo0kSAsax9LbMQPlpuMTkQ657idF2ehOYe4Iw+8vj7vfAYa39Ii9IlaVAFMC1FxCYzLNFz+leZBm/Stn/NA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "apollo-server-core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.3.0.tgz", - "integrity": "sha512-KmkzKVG3yjybouDyUX6Melv39u1EOFipvAKP17IlPis/TjVbubJmb6hkE0am/g2RipyhRvlpxAjHqPaCTXR1dQ==", - "requires": { - "@apollographql/apollo-tools": "^0.5.1", - "@apollographql/graphql-playground-html": "1.6.29", - "@graphql-tools/mock": "^8.1.2", - "@graphql-tools/schema": "^8.0.0", - "@graphql-tools/utils": "^8.0.0", - "@josephg/resolvable": "^1.0.0", - "apollo-datasource": "^3.1.0", - "apollo-graphql": "^0.9.0", - "apollo-reporting-protobuf": "^3.0.0", - "apollo-server-caching": "^3.1.0", - "apollo-server-env": "^4.0.3", - "apollo-server-errors": "^3.1.0", - "apollo-server-plugin-base": "^3.2.0", - "apollo-server-types": "^3.2.0", - "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.1.0", - "graphql-tag": "^2.11.0", - "loglevel": "^1.6.8", - "lru-cache": "^6.0.0", - "sha.js": "^2.4.11", - "uuid": "^8.0.0" - } - }, - "apollo-server-env": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.0.3.tgz", - "integrity": "sha512-B32+RUOM4GUJAwnQqQE1mT1BG7+VfW3a0A87Bp3gv/q8iNnhY2BIWe74Qn03pX8n27g3EGVCt0kcBuHhjG5ltA==", - "requires": { - "node-fetch": "^2.6.1" - } - }, - "apollo-server-errors": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.1.0.tgz", - "integrity": "sha512-bUmobPEvtcBFt+OVHYqD390gacX/Cm5s5OI5gNZho8mYKAA6OjgnRlkm/Lti6NzniXVxEQyD5vjkC6Ox30mGFg==", - "requires": {} - }, - "apollo-server-express": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.3.0.tgz", - "integrity": "sha512-qJedh77IxbfT+HpYsDraC2CGdy08wiWTwoKYXjRK4S/DHbe94A4957/1blw4boYO4n44xRKQd1k6zxiixCp+XQ==", - "requires": { - "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.1", - "@types/cors": "2.8.12", - "@types/express": "4.17.13", - "@types/express-serve-static-core": "4.17.24", - "accepts": "^1.3.5", - "apollo-server-core": "^3.3.0", - "apollo-server-types": "^3.2.0", - "body-parser": "^1.19.0", - "cors": "^2.8.5", - "parseurl": "^1.3.3" - } - }, - "apollo-server-plugin-base": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.2.0.tgz", - "integrity": "sha512-anjyiw79wxU4Cj2bYZFWQqZPjuaZ4mVJvxCoyvkFrNvjPua9dovCOfpng43C5NwdsqJpz78Vqs236eFM2QoeaA==", - "requires": { - "apollo-server-types": "^3.2.0" - } - }, - "apollo-server-types": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.2.0.tgz", - "integrity": "sha512-Fh7QP84ufDZHbLzoLyyxyzznlW8cpgEZYYkGsS1i36zY4VaAt5OUOp1f+FxWdLGehq0Arwb6D1W7y712IoZ/JQ==", - "requires": { - "apollo-reporting-protobuf": "^3.0.0", - "apollo-server-caching": "^3.1.0", - "apollo-server-env": "^4.0.3" - } - }, - "arango-express": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/arango-express/-/arango-express-1.0.0.tgz", - "integrity": "sha512-PNlQE5Z1ftZcwAHZgBY7Hx/18EE0CgRTr7XIFGgMVK87Ns0ym6kfReZyYH3J7zWrwzVwTjmA99gTRnW/28zFEQ==", - "requires": { - "arangojs": "^7.5.0" - } - }, - "arango-tools": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.5.0.tgz", - "integrity": "sha512-Fb6gnfvtmqSH1lyHwiL3tKugOwRa8d1xPBrW+JF0ZUvZnAPS5PC2EHUYSzZoGurJa5DHHpkIz+azFeF9fbQniw==", - "requires": { - "arangojs": "^7.2.0", - "assign-deep": "^1.0.1" - } - }, - "arangojs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.5.0.tgz", - "integrity": "sha512-9taMv73bo0O/bpFeqcT4xnYzu5yg+UZ9qGQ9SCAQXnKZZcOAwP1TixvOBv21il7XIzX28RCVoH+wp/u8Ajb8+Q==", - "requires": { - "@types/node": ">=13.13.4", - "es6-error": "^4.0.1", - "multi-part": "^3.0.0", - "x3-linkedlist": "1.2.0", - "xhr": "^2.4.1" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "optional": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "optional": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "optional": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "optional": true - }, - "array.prototype.flat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", - "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "assign-deep": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", - "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", - "requires": { - "assign-symbols": "^2.0.2" - } - }, - "assign-symbols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", - "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true, - "optional": true - }, - "async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "requires": { - "retry": "0.13.1" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "optional": true - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true, - "requires": {} - }, - "babel-jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.0.tgz", - "integrity": "sha512-bS2p+KGGVVmWXBa8+i6SO/xzpiz2Q/2LnqLbQknPKefWXVZ67YIjA4iXup/jMOEZplga9PpWn+wrdb3UdDwRaA==", - "dev": true, - "requires": { - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", - "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", - "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.2", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.4.tgz", - "integrity": "sha512-z3HnJE5TY/j4EFEa/qpQMSbcUJZ5JQi+3UFjXzn6pQCmIKc5Ug5j98SuYyH+m4xQnvKlMDIW4plLfgyVnd0IcQ==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.14.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", - "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2" - } - }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", - "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^27.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "optional": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "bcp47": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", - "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=" - }, - "bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "brcast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.2.tgz", - "integrity": "sha512-f5XwwFCCuvgqP2nMH/hJ74FqnGmb4X3D+NC//HphxJzzhsZvSZa+Hk/syB7j3ZHpPDLMoYU8oBgviRWfNvEfKA==" - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz", - "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001254", - "colorette": "^1.3.0", - "electron-to-chromium": "^1.3.830", - "escalade": "^3.1.1", - "node-releases": "^1.1.75" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "optional": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001257", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", - "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "change-emitter": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", - "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true - }, - "ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - } - }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", - "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", - "dev": true - }, - "cli-table": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", - "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", - "dev": true, - "requires": { - "colors": "1.0.3" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, - "clipboard": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", - "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", - "requires": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "cluster-key-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", - "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "optional": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "commonmark": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz", - "integrity": "sha512-fvt/NdOFKaL2gyhltSy6BC4LxbbxbnPxBMl923ittqO/JBM0wQHaoYZliE4tp26cRxX/ZZtRsJlZzQrVdUkXAA==", - "requires": { - "entities": "~2.0", - "mdurl": "~1.0.1", - "minimist": ">=1.2.2", - "string.prototype.repeat": "^0.2.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - } - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-parser": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", - "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", - "requires": { - "cookie": "0.4.0", - "cookie-signature": "1.0.6" - } - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "optional": true - }, - "core-js": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.3.tgz", - "integrity": "sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==", - "dev": true - }, - "core-js-compat": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.17.3.tgz", - "integrity": "sha512-+in61CKYs4hQERiADCJsdgewpdl/X0GhEX77pjKgbeibXviIt2oxEjTc8O2fqHX8mDdBrDvX8MYD/RYsBv4OiA==", - "dev": true, - "requires": { - "browserslist": "^4.17.0", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "core-js-pure": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.17.3.tgz", - "integrity": "sha512-YusrqwiOTTn8058JDa0cv9unbXdIiIgcgI9gXso0ey4WgkFLd3lYlV9rp9n7nDCsYxXsMDTjA4m1h3T348mdlQ==" - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "optional": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-fetch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", - "integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==", - "dev": true, - "requires": { - "node-fetch": "2.6.1" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true - } - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "css-vendor": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", - "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", - "requires": { - "is-in-browser": "^1.0.2" - } - }, - "cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "csstype": { - "version": "2.6.18", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", - "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "dataloader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", - "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" - }, - "date-fns": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.23.0.tgz", - "integrity": "sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==", - "dev": true - }, - "debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "optional": true - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" - }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" - }, - "dotenv-safe": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", - "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", - "requires": { - "dotenv": "^8.2.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "electron-to-chromium": { - "version": "1.3.840", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz", - "integrity": "sha512-yRoUmTLDJnkIJx23xLY7GbSvnmDCq++NSuxHDQ0jiyDJ9YZBUGJcrdUqm+ZwZFzMbCciVzfem2N2AWiHJcWlbw==", - "dev": true - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "requires": { - "iconv-lite": "^0.6.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", - "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", - "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-string": "^1.0.7", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - } - }, - "es-module-lexer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==", - "dev": true, - "peer": true - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true, - "requires": {} - }, - "eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz", - "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - } - }, - "eslint-plugin-import": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", - "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", - "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.6.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.4", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - } - } - }, - "eslint-plugin-jest": { - "version": "24.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.4.0.tgz", - "integrity": "sha512-8qnt/hgtZ94E9dA6viqfViKBfkJwFHXgJmTWlMGDgunw1XJEGqm3eiPjDsTanM3/u/3Az82nyQM9GX7PM/QGmg==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "^4.0.1" - } - }, - "eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", - "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "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": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "optional": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - } - } - }, - "expect": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.0.tgz", - "integrity": "sha512-oOTbawMQv7AK1FZURbPTgGSzmhxkjFzoARSvDjOMnOpeWuYQx1tP6rXu9MIX5mrACmyCAM7fSNP8IJO2f1p0CQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-regex-util": "^27.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "express-request-language": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/express-request-language/-/express-request-language-1.1.15.tgz", - "integrity": "sha512-KiLUdEZCcgwh8qfIvkCrhz1MMAFx/Xj4UcspN4zUxVdp+bp+yFvqUMmlyMHK2nC5JlQV7VK5uFOoS5LrArTL1A==", - "requires": { - "accept-language": "^3.0.4", - "bcp47": "^1.1.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "optional": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-type": { - "version": "12.4.2", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", - "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==" - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "optional": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "optional": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "fuzzaldrin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", - "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "optional": true - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "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-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dev": true, - "requires": { - "ini": "1.3.7" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - } - } - }, - "good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "requires": { - "delegate": "^3.1.2" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "graphql": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.3.tgz", - "integrity": "sha512-sM+jXaO5KinTui6lbK/7b7H/Knj9BpjGxZ+Ki35v7YbUJxxdBCUqNM0h3CRVU1ZF9t5lNiBzvBCSYPvIwxPOQA==" - }, - "graphql-depth-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", - "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", - "requires": { - "arrify": "^1.0.1" - } - }, - "graphql-playground-html": { - "version": "1.6.29", - "resolved": "https://registry.npmjs.org/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", - "integrity": "sha512-fbF/zZKuw2sdfKp8gjTORJ/I9xBsqeEYRseWxBzuR15NHMptRTT9414IyRCs3ognZzUDr5MDJgx97SlLZCtQyA==", - "requires": { - "xss": "^1.0.6" - } - }, - "graphql-playground-middleware-express": { - "version": "1.7.22", - "resolved": "https://registry.npmjs.org/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.22.tgz", - "integrity": "sha512-PJLiCxLmN6Dp+dHGyHU92m9y3hB/RAkcUBWcqYl2fiP+EbpDDgNfElrsVzW60MhJe+LTV1PFqiInH2d3KNvlCQ==", - "requires": { - "graphql-playground-html": "^1.6.29" - } - }, - "graphql-redis-subscriptions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.4.0.tgz", - "integrity": "sha512-McIJK9R8sVbjTlkkaFMRAG5dOF6CPEPO+8PcyHlKARd64d4h4bwl0q/DnR2RA494ZrfykEvFWAnwuIM45470Vw==", - "requires": { - "ioredis": "^4.17.3", - "iterall": "^1.3.0" - } - }, - "graphql-relay": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.9.0.tgz", - "integrity": "sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==", - "requires": {} - }, - "graphql-scalars": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.10.1.tgz", - "integrity": "sha512-/6vDkUogePfumSJaCRVrVT1vpEhvE0m36AHTPzhLC/tucWxVolOCj0EgGKVUMc0uehe93ByuRBj8rEDaPRXVxg==", - "requires": { - "tslib": "~2.3.0" - } - }, - "graphql-subscriptions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz", - "integrity": "sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g==", - "requires": { - "iterall": "^1.3.0" - } - }, - "graphql-tag": { - "version": "2.12.5", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.5.tgz", - "integrity": "sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ==", - "requires": { - "tslib": "^2.1.0" - } - }, - "graphql-validation-complexity": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/graphql-validation-complexity/-/graphql-validation-complexity-0.4.2.tgz", - "integrity": "sha512-4tzmN/70a06c2JH5fvISkoLX6oBDpqK22cvr2comge3HZHtBLD3n5Sl6MnQYMVhQqKGlpZWcCgD00MnyKNzYYg==", - "requires": { - "warning": "^4.0.3" - } - }, - "graphql-voyager": { - "version": "1.0.0-rc.31", - "resolved": "https://registry.npmjs.org/graphql-voyager/-/graphql-voyager-1.0.0-rc.31.tgz", - "integrity": "sha512-eCaFL8niR3DZo1LUcFV8txwR+MDVIQsrFPmFC7Zwab8UyH5x3SLhx3aDrt998RVJRmY5aFP4q79RY2F3QtGRqA==", - "requires": { - "@f/animate": "^1.0.1", - "@material-ui/core": "^3.9.3", - "classnames": "^2.2.6", - "clipboard": "^2.0.4", - "commonmark": "^0.29.0", - "lodash": "^4.17.10", - "prop-types": "^15.7.2", - "svg-pan-zoom": "^3.6.0", - "viz.js": "2.1.2" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "optional": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "optional": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "dependencies": { - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indefinite-observable": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz", - "integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==", - "requires": { - "symbol-observable": "1.2.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ioredis": { - "version": "4.27.9", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.9.tgz", - "integrity": "sha512-hAwrx9F+OQ0uIvaJefuS3UTqW+ByOLyLIV+j0EH8ClNVxvFyH9Vmb08hCL4yje6mDYT5zMquShhypkd50RRzkg==", - "requires": { - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.1", - "denque": "^1.1.0", - "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", - "lodash.isarguments": "^3.1.0", - "p-map": "^2.1.0", - "redis-commands": "1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "requires": { - "ci-info": "^3.1.1" - } - }, - "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "optional": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "optional": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" - }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "optional": true - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" - }, - "jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.2.0.tgz", - "integrity": "sha512-oUqVXyvh5YwEWl263KWdPUAqEzBFzGHdFLQ05hUnITr1tH+9SscEI9A/GH9eBClA+Nw1ct+KNuuOV6wlnmBPcg==", - "dev": true, - "requires": { - "@jest/core": "^27.2.0", - "import-local": "^3.0.2", - "jest-cli": "^27.2.0" - } - }, - "jest-changed-files": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.1.1.tgz", - "integrity": "sha512-5TV9+fYlC2A6hu3qtoyGHprBwCAn0AuGA77bZdUgYvVlRMjHXo063VcWTEAyx6XAZ85DYHqp0+aHKbPlfRDRvA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - } - }, - "jest-circus": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.0.tgz", - "integrity": "sha512-WwENhaZwOARB1nmcboYPSv/PwHBUGRpA4MEgszjr9DLCl97MYw0qZprBwLb7rNzvMwfIvNGG7pefQ5rxyBlzIA==", - "dev": true, - "requires": { - "@jest/environment": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.2.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.2.0", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "pretty-format": "^27.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.0.tgz", - "integrity": "sha512-bq1X/B/b1kT9y1zIFMEW3GFRX1HEhFybiqKdbxM+j11XMMYSbU9WezfyWIhrSOmPT+iODLATVjfsCnbQs7cfIA==", - "dev": true, - "requires": { - "@jest/core": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.2.0.tgz", - "integrity": "sha512-Z1romHpxeNwLxQtouQ4xt07bY6HSFGKTo0xJcvOK3u6uJHveA4LB2P+ty9ArBLpTh3AqqPxsyw9l9GMnWBYS9A==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.2.0", - "@jest/types": "^27.1.1", - "babel-jest": "^27.2.0", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.2.0", - "jest-environment-jsdom": "^27.2.0", - "jest-environment-node": "^27.2.0", - "jest-get-type": "^27.0.6", - "jest-jasmine2": "^27.2.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.2.0", - "jest-runner": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "micromatch": "^4.0.4", - "pretty-format": "^27.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - } - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-diff": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.0.tgz", - "integrity": "sha512-QSO9WC6btFYWtRJ3Hac0sRrkspf7B01mGrrQEiCW6TobtViJ9RWL0EmOs/WnBsZDsI/Y2IoSHZA2x6offu0sYw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.2.0.tgz", - "integrity": "sha512-biDmmUQjg+HZOB7MfY2RHSFL3j418nMoC3TK3pGAj880fQQSxvQe1y2Wy23JJJNUlk6YXiGU0yWy86Le1HBPmA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "jest-util": "^27.2.0", - "pretty-format": "^27.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.0.tgz", - "integrity": "sha512-wNQJi6Rd/AkUWqTc4gWhuTIFPo7tlMK0RPZXeM6AqRHZA3D3vwvTa9ktAktyVyWYmUoXdYstOfyYMG3w4jt7eA==", - "dev": true, - "requires": { - "@jest/environment": "^27.2.0", - "@jest/fake-timers": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "jest-mock": "^27.1.1", - "jest-util": "^27.2.0", - "jsdom": "^16.6.0" - } - }, - "jest-environment-node": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.0.tgz", - "integrity": "sha512-WbW+vdM4u88iy6Q3ftUEQOSgMPtSgjm3qixYYK2AKEuqmFO2zmACTw1vFUB0qI/QN88X6hA6ZkVKIdIWWzz+yg==", - "dev": true, - "requires": { - "@jest/environment": "^27.2.0", - "@jest/fake-timers": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "jest-mock": "^27.1.1", - "jest-util": "^27.2.0" - } - }, - "jest-fetch-mock": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", - "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", - "dev": true, - "requires": { - "cross-fetch": "^3.0.4", - "promise-polyfill": "^8.1.3" - } - }, - "jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true - }, - "jest-haste-map": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.0.tgz", - "integrity": "sha512-laFet7QkNlWjwZtMGHCucLvF8o9PAh2cgePRck1+uadSM4E4XH9J4gnx4do+a6do8ZV5XHNEAXEkIoNg5XUH2Q==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-jasmine2": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.0.tgz", - "integrity": "sha512-NcPzZBk6IkDW3Z2V8orGueheGJJYfT5P0zI/vTO/Jp+R9KluUdgFrgwfvZ0A34Kw6HKgiWFILZmh3oQ/eS+UxA==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.2.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.2.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.2.0", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "pretty-format": "^27.2.0", - "throat": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.0.tgz", - "integrity": "sha512-e91BIEmbZw5+MHkB4Hnrq7S86coTxUMCkz4n7DLmQYvl9pEKmRx9H/JFH87bBqbIU5B2Ju1soKxRWX6/eGFGpA==", - "dev": true, - "requires": { - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - } - }, - "jest-matcher-utils": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.0.tgz", - "integrity": "sha512-F+LG3iTwJ0gPjxBX6HCyrARFXq6jjiqhwBQeskkJQgSLeF1j6ui1RTV08SR7O51XTUhtc8zqpDj8iCG4RGmdKw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.2.0", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.0.tgz", - "integrity": "sha512-y+sfT/94CiP8rKXgwCOzO1mUazIEdEhrLjuiu+RKmCP+8O/TJTSne9dqQRbFIHBtlR2+q7cddJlWGir8UATu5w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.1.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-mock": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.1.1.tgz", - "integrity": "sha512-SClsFKuYBf+6SSi8jtAYOuPw8DDMsTElUWEae3zq7vDhH01ayVSIHUSIa8UgbDOUalCFp6gNsaikN0rbxN4dbw==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", - "dev": true - }, - "jest-resolve": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.0.tgz", - "integrity": "sha512-v09p9Ib/VtpHM6Cz+i9lEAv1Z/M5NVxsyghRHRMEUOqwPQs3zwTdwp1xS3O/k5LocjKiGS0OTaJoBSpjbM2Jlw==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "resolve": "^1.20.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.0.tgz", - "integrity": "sha512-EY5jc/Y0oxn+oVEEldTidmmdVoZaknKPyDORA012JUdqPyqPL+lNdRyI3pGti0RCydds6coaw6xt4JQY54dKsg==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.2.0" - } - }, - "jest-runner": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.0.tgz", - "integrity": "sha512-Cl+BHpduIc0cIVTjwoyx0pQk4Br8gn+wkr35PmKCmzEdOUnQ2wN7QVXA8vXnMQXSlFkN/+KWnk20TAVBmhgrww==", - "dev": true, - "requires": { - "@jest/console": "^27.2.0", - "@jest/environment": "^27.2.0", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.2.0", - "jest-environment-node": "^27.2.0", - "jest-haste-map": "^27.2.0", - "jest-leak-detector": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-resolve": "^27.2.0", - "jest-runtime": "^27.2.0", - "jest-util": "^27.2.0", - "jest-worker": "^27.2.0", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.0.tgz", - "integrity": "sha512-6gRE9AVVX49hgBbWQ9PcNDeM4upMUXzTpBs0kmbrjyotyUyIJixLPsYjpeTFwAA07PVLDei1iAm2chmWycdGdQ==", - "dev": true, - "requires": { - "@jest/console": "^27.2.0", - "@jest/environment": "^27.2.0", - "@jest/fake-timers": "^27.2.0", - "@jest/globals": "^27.2.0", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.2.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-mock": "^27.1.1", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.2.0", - "jest-snapshot": "^27.2.0", - "jest-util": "^27.2.0", - "jest-validate": "^27.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-validate": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.0.tgz", - "integrity": "sha512-uIEZGkFKk3+4liA81Xu0maG5aGDyPLdp+4ed244c+Ql0k3aLWQYcMbaMLXOIFcb83LPHzYzqQ8hwNnIxTqfAGQ==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.2.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.0.tgz", - "integrity": "sha512-MukJvy3KEqemCT2FoT3Gum37CQqso/62PKTfIzWmZVTsLsuyxQmJd2PI5KPcBYFqLlA8LgZLHM8ZlazkVt8LsQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.2.0", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.2.0", - "jest-get-type": "^27.0.6", - "jest-haste-map": "^27.2.0", - "jest-matcher-utils": "^27.2.0", - "jest-message-util": "^27.2.0", - "jest-resolve": "^27.2.0", - "jest-util": "^27.2.0", - "natural-compare": "^1.4.0", - "pretty-format": "^27.2.0", - "semver": "^7.3.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.2.0.tgz", - "integrity": "sha512-T5ZJCNeFpqcLBpx+Hl9r9KoxBCUqeWlJ1Htli+vryigZVJ1vuLB9j35grEBASp4R13KFkV7jM52bBGnArpJN6A==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.0.tgz", - "integrity": "sha512-SjRWhnr+qO8aBsrcnYIyF+qRxNZk6MZH8TIDgvi+VlsyrvOyqg0d+Rm/v9KHiTtC9mGGeFi9BFqgavyWib6xLg==", - "dev": true, - "requires": { - "@jest/test-result": "^27.2.0", - "@jest/types": "^27.1.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.2.0", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.0.tgz", - "integrity": "sha512-laB0ZVIBz+voh/QQy9dmUuuDsadixeerrKqyVpgPz+CCWiOYjOBabUXHIXZhsdvkWbLqSHbgkAHWl5cg24Q6RA==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "jss": { - "version": "9.8.7", - "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", - "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", - "requires": { - "is-in-browser": "^1.1.3", - "symbol-observable": "^1.1.0", - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, - "jss-camel-case": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz", - "integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==", - "requires": { - "hyphenate-style-name": "^1.0.2" - } - }, - "jss-default-unit": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", - "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==", - "requires": {} - }, - "jss-global": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", - "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==", - "requires": {} - }, - "jss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz", - "integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==", - "requires": { - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, - "jss-props-sort": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", - "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==", - "requires": {} - }, - "jss-vendor-prefixer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz", - "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==", - "requires": { - "css-vendor": "^0.3.8" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, - "peer": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "make-plural": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.2.tgz", - "integrity": "sha512-8iTuFioatnTTmb/YJjywkVIHLjcwkFD9Ms0JpxjEm9Mo8eQYkh1z+55dwv4yc1jQ8ftVBxWQbihvZL1DfzGGWA==" - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "optional": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "optional": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "messageformat-parser": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", - "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" - }, - "mime-kind": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", - "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", - "requires": { - "file-type": "^12.1.0", - "mime-types": "^2.1.24" - } - }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "requires": { - "mime-db": "1.49.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "optional": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multi-part": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", - "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", - "requires": { - "mime-kind": "^3.0.0", - "multi-part-lite": "^1.0.0" - } - }, - "multi-part-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", - "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==" - }, - "node-gettext": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", - "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", - "dev": true, - "requires": { - "lodash.get": "^4.4.2" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-releases": { - "version": "1.1.75", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", - "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", - "dev": true - }, - "nodemon": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", - "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", - "dev": true, - "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-scroll-left": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz", - "integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg==" - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "notifications-node-client": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-5.1.0.tgz", - "integrity": "sha512-a3aoSZPHSc/8VaccfGvKKsIZ/crqbglP9dNvg0pHHTgWi6BYiJc+Md7wOPizzEPACa+SKdifs06VY8ktbTzySA==", - "requires": { - "axios": "^0.21.1", - "jsonwebtoken": "^8.2.1", - "underscore": "^1.9.0" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "optional": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "optional": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "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.3" - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - } - }, - "papaparse": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", - "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-headers": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", - "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==" - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@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" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "optional": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "plurals-cldr": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/plurals-cldr/-/plurals-cldr-1.0.4.tgz", - "integrity": "sha512-4nLXqtel7fsCgzi8dvRZvUjfL8SXpP982sKg7b2TgpnR8rDnes06iuQ83trQ/+XdtyMIQkBBbKzX6x97eLfsJQ==", - "dev": true - }, - "pofile": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.1.tgz", - "integrity": "sha512-RVAzFGo1Mx9+YukVKSgTLut6r4ZVBW8IVrqGHAPfEsVJN93WSp5HRD6+qNa7av1q/joPKDNJd55m5AJl9GBQGA==", - "dev": true - }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "optional": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, - "prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", - "dev": true - }, - "pretty-format": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz", - "integrity": "sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==", - "dev": true, - "requires": { - "@jest/types": "^27.1.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "optional": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, - "promise-polyfill": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz", - "integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==", - "dev": true - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pseudolocale": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.2.0.tgz", - "integrity": "sha512-k0OQFvIlvpRdzR0dPVrrbWX7eE9EaZ6gpZtTlFSDi1Gf9tMy9wiANCNu7JZ0drcKgUri/39a2mBbH0goiQmrmQ==", - "dev": true, - "requires": { - "commander": "*" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, - "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "react-event-listener": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", - "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", - "requires": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "requires": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "recompose": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", - "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", - "requires": { - "@babel/runtime": "^7.0.0", - "change-emitter": "^0.1.2", - "fbjs": "^0.8.1", - "hoist-non-react-statics": "^2.3.1", - "react-lifecycles-compat": "^3.0.2", - "symbol-observable": "^1.0.4" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - } - } - }, - "redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "requires": { - "redis-errors": "^1.0.0" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true, - "optional": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "optional": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "optional": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true, - "optional": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "optional": true - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "optional": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "peer": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "peer": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "optional": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "optional": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "optional": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true, - "optional": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^3.0.0" - }, - "dependencies": { - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "stack-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.4.tgz", - "integrity": "sha512-ERg+H//lSSYlZhBIUu+wJnqg30AbyBbpZlIhcshpn7BNzpoRODZgfyr9J+8ERf3ooC6af3u7Lcl01nleau7MrA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0", - "source-map-support": "^0.5.20" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "optional": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.repeat": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", - "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", - "requires": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - } - }, - "superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dev": true, - "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "dependencies": { - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true - }, - "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "supertest": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.6.tgz", - "integrity": "sha512-0hACYGNJ8OHRg8CRITeZOdbjur7NLuNs0mBjVhdpxi7hP6t3QIbOzLON5RTUmZcy2I9riuII3+Pr2C7yztrIIg==", - "dev": true, - "requires": { - "methods": "^1.1.2", - "superagent": "^6.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "svg-pan-zoom": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/svg-pan-zoom/-/svg-pan-zoom-3.6.1.tgz", - "integrity": "sha512-JaKkGHHfGvRrcMPdJWkssLBeWqM+Isg/a09H7kgNNajT1cX5AztDTNs+C8UzpCxjCTRrG34WbquwaovZbmSk9g==" - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true - }, - "term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", - "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", - "dev": true, - "peer": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "peer": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", - "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, - "peer": true, - "requires": { - "jest-worker": "^27.0.6", - "p-limit": "^3.1.0", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "peer": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "optional": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "dependencies": { - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - } - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - }, - "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", - "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", - "dev": true, - "peer": true - }, - "ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==" - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "optional": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "optional": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "optional": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "optional": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true - }, - "update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dev": true, - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true, - "optional": true - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, - "url-slug": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/url-slug/-/url-slug-3.0.2.tgz", - "integrity": "sha512-puioWUGY+esk4kKW8L6fCZWb+xK1+0L/KH2miV6GEJdlCJRJ2lfRlvHkUikyEU1e1v4j1C1HBQKvuljFOxmnEA==" - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", - "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validator": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", - "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" - }, - "value-or-promise": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.10.tgz", - "integrity": "sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "viz.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-2.1.2.tgz", - "integrity": "sha512-UO6CPAuEMJ8oNR0gLLNl+wUiIzQUsyUOp8SyyDKTqVRBtq7kk1VnFmIZW8QufjxGrGEuI+LVR7p/C7uEKy0LQw==" - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", - "dev": true, - "peer": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "webpack": { - "version": "5.52.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz", - "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.0", - "es-module-lexer": "^0.7.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" - }, - "dependencies": { - "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true, - "peer": true - }, - "acorn-import-assertions": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", - "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", - "dev": true, - "peer": true, - "requires": {} - } - } - }, - "webpack-sources": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.1.tgz", - "integrity": "sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==", - "dev": true, - "peer": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "requires": {} - }, - "x3-linkedlist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", - "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==" - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, - "xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", - "requires": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "xss": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz", - "integrity": "sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ==", - "requires": { - "commander": "^2.20.3", - "cssfilter": "0.0.10" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - } - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "peer": true - } - } -} diff --git a/api-js/package.json b/api-js/package.json deleted file mode 100644 index 61e4163d58..0000000000 --- a/api-js/package.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "name": "tracker-api", - "version": "1.0.0", - "repository": { - "type": "git", - "url": "https://github.com/canada-ca/tracker/tree/master/api-js" - }, - "author": "nsdeschenes", - "license": "MIT", - "scripts": { - "build": "npm run clean && babel ./src --out-dir dist/src --ignore 'src/**/*.spec.js','src/**/*.test.js' && babel index.js database-options.js --out-dir dist/", - "start": "node ./dist/index.js", - "dev": "nodemon --exec babel-node index.js", - "clean": "rm -rf ./dist && mkdir dist", - "test": "jest", - "test-remainder": "jest --testPathIgnorePatterns=.*-scan-data.*", - "test-subscriptions": "jest --runInBand --testPathPattern=.*-scan-data.*", - "test-coverage": "jest --coverage", - "lint": "eslint src", - "prettier": "prettier --write \"**/*.js\"", - "extract": "npx lingui extract", - "compile": "npx lingui compile", - "lin-clean": "npx lingui extract --clean" - }, - "dependencies": { - "@lingui/core": "^3.11.1", - "apollo-server": "^3.3.0", - "apollo-server-core": "^3.3.0", - "apollo-server-express": "^3.3.0", - "arango-express": "^1.0.0", - "arango-tools": "^0.5.0", - "arangojs": "^7.5.0", - "bcryptjs": "^2.4.3", - "cookie-parser": "^1.4.5", - "cors": "^2.8.5", - "crypto": "^1.0.1", - "dataloader": "^2.0.0", - "dotenv-safe": "^8.2.0", - "express": "^4.17.1", - "express-request-language": "^1.1.15", - "graphql": "^15.5.3", - "graphql-depth-limit": "^1.1.0", - "graphql-playground-middleware-express": "^1.7.22", - "graphql-redis-subscriptions": "^2.4.0", - "graphql-relay": "^0.9.0", - "graphql-scalars": "^1.10.1", - "graphql-subscriptions": "^1.2.1", - "graphql-validation-complexity": "^0.4.2", - "graphql-voyager": "^1.0.0-rc.31", - "ioredis": "^4.27.9", - "isomorphic-fetch": "^3.0.0", - "jsonwebtoken": "^8.5.1", - "moment": "^2.29.1", - "notifications-node-client": "^5.1.0", - "subscriptions-transport-ws": "^0.9.19", - "url-slug": "^3.0.2", - "uuid": "^8.3.2", - "validator": "^13.6.0" - }, - "devDependencies": { - "@babel/cli": "^7.15.4", - "@babel/core": "^7.15.5", - "@babel/node": "^7.15.4", - "@babel/preset-env": "^7.15.6", - "@jest/test-sequencer": "^27.2.0", - "@lingui/cli": "^3.11.1", - "@lingui/loader": "^3.11.1", - "@lingui/macro": "^3.11.1", - "babel-core": "^7.0.0-bridge.0", - "babel-jest": "^27.2.0", - "babel-plugin-macros": "^3.1.0", - "babel-polyfill": "^6.26.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.4.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "jest": "^27.2.0", - "jest-fetch-mock": "^3.0.3", - "jest-matcher-utils": "^27.2.0", - "nodemon": "^2.0.12", - "prettier": "^2.4.1", - "supertest": "^6.1.6" - } -} diff --git a/api-js/src/__tests__/create-context.test.js b/api-js/src/__tests__/create-context.test.js deleted file mode 100644 index 543ad7555d..0000000000 --- a/api-js/src/__tests__/create-context.test.js +++ /dev/null @@ -1,51 +0,0 @@ -const { createContext } = require('../create-context') -const { tokenize } = require('../auth') - -describe('given the create context function', () => { - const consoleOut = [] - const mockedWarn = (output) => consoleOut.push(output) - console.warn = mockedWarn - - describe('request authorization token is not set', () => { - it('returns object with userKey as undefined', async () => { - const context = await createContext({})({ - req: { headers: { authorization: '' }, language: 'en' }, - res: {}, - }) - - expect(context.userKey).toEqual(undefined) - }) - }) - describe('request authorization token is set', () => { - it('returns object with userKey as value', async () => { - const token = tokenize({ parameters: { userKey: '1234' } }) - const context = await createContext({})({ - req: { headers: { authorization: token }, language: 'en' }, - res: {}, - }) - - expect(context.userKey).toEqual('1234') - }) - }) - describe('connection value is set', () => { - it('returns an object with set token', async () => { - const token = tokenize({ parameters: { userKey: '1234' } }) - const context = await createContext({})({ - req: {}, - res: {}, - connection: { authorization: token }, - }) - - expect(context.userKey).toEqual('1234') - }) - it('returns an object with a set language', async () => { - const context = await createContext({})({ - req: {}, - res: {}, - connection: { language: 'en' }, - }) - - expect(context.request.language).toEqual('en') - }) - }) -}) diff --git a/api-js/src/__tests__/on-connect.test.js b/api-js/src/__tests__/on-connect.test.js deleted file mode 100644 index e467302bb1..0000000000 --- a/api-js/src/__tests__/on-connect.test.js +++ /dev/null @@ -1,190 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' - -import { createContext } from '../create-context' -import { customOnConnect } from '../on-connect' -import { verifyToken, tokenize, userRequired } from '../auth' -import { createI18n } from '../create-i18n' -import { databaseOptions } from '../../database-options' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the customOnConnect function', () => { - let mockedUserRequired - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - mockedUserRequired = jest.fn().mockReturnValue(() => true) - }) - - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('checking that language is set', () => { - describe('language is set to english', () => { - it('is set in the returned object', async () => { - const token = tokenize({ parameters: { userKey: '1234' } }) - - const webSocket = { - upgradeReq: {}, - } - - const connectionParams = { - authorization: token, - AcceptLanguage: 'en', - } - - const onConnect = await customOnConnect({ - createContext, - serverContext: {}, - createI18n, - verifyToken, - userRequired: mockedUserRequired, - loadUserByKey: jest.fn(), - verifiedRequired: jest.fn(), - })(connectionParams, webSocket, { request: {} }) - - expect(onConnect.request.language).toEqual('en') - expect(consoleOutput).toEqual([ - 'User: 1234, connected to subscription.', - ]) - }) - }) - describe('language is set to french', () => { - it('is set in the returned object', async () => { - const token = tokenize({ parameters: { userKey: '1234' } }) - - const webSocket = { - upgradeReq: {}, - } - - const connectionParams = { - authorization: token, - AcceptLanguage: 'fr', - } - - const onConnect = await customOnConnect({ - createContext, - serverContext: {}, - createI18n, - verifyToken, - userRequired: mockedUserRequired, - loadUserByKey: jest.fn(), - verifiedRequired: jest.fn(), - })(connectionParams, webSocket, { request: {} }) - - expect(onConnect.request.language).toEqual('fr') - expect(consoleOutput).toEqual([ - 'User: 1234, connected to subscription.', - ]) - }) - }) - }) - describe('authorization token is set', () => { - it('has authorization set in the returned object', async () => { - const token = tokenize({ parameters: { userKey: '1234' } }) - - const webSocket = { - upgradeReq: {}, - } - - const connectionParams = { - authorization: token, - AcceptLanguage: 'en', - } - - const onConnect = await customOnConnect({ - createContext, - serverContext: {}, - createI18n, - verifyToken, - userRequired: mockedUserRequired, - loadUserByKey: jest.fn(), - verifiedRequired: jest.fn(), - })(connectionParams, webSocket, { request: {} }) - - expect(onConnect.request.headers.authorization).toEqual(token) - expect(consoleOutput).toEqual(['User: 1234, connected to subscription.']) - }) - }) - describe('authorization token is not set', () => { - let query, drop, truncate - - beforeAll(async () => { - ;({ query, drop, truncate } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await drop() - }) - - describe('language is set to english', () => { - it('throws an error', async () => { - const webSocket = { - upgradeReq: {}, - } - - const connectionParams = { AcceptLanguage: 'en' } - - try { - await customOnConnect({ - createContext, - serverContext: { query }, - createI18n, - verifyToken, - userRequired, - loadUserByKey: jest.fn().mockReturnValue({ load: jest.fn() }), - })(connectionParams, webSocket, { request: {} }) - } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) - } - expect(consoleOutput).toEqual([ - 'User attempted to access controlled content, but userKey was undefined.', - ]) - }) - }) - describe('language is set to french', () => { - it('throws an error', async () => { - const webSocket = { - upgradeReq: {}, - } - - const connectionParams = { AcceptLanguage: 'fr' } - - try { - await customOnConnect({ - createContext, - serverContext: { query }, - createI18n, - verifyToken, - userRequired, - loadUserByKey: jest.fn().mockReturnValue({ load: jest.fn() }), - })(connectionParams, webSocket, { request: {} }) - } catch (err) { - expect(err).toEqual( - new Error("Erreur d'authentification. Veuillez vous connecter."), - ) - } - expect(consoleOutput).toEqual([ - 'User attempted to access controlled content, but userKey was undefined.', - ]) - }) - }) - }) -}) diff --git a/api-js/src/__tests__/server.test.js b/api-js/src/__tests__/server.test.js deleted file mode 100644 index cab8194bf2..0000000000 --- a/api-js/src/__tests__/server.test.js +++ /dev/null @@ -1,183 +0,0 @@ -import request from 'supertest' -import { Server } from '../server' -import { ensure, dbNameFromFile } from 'arango-tools' -import { databaseOptions } from '../../database-options' - -const { - DB_PASS: rootPass, - DB_URL: url, - DEPTH_LIMIT: maxDepth, - COST_LIMIT: complexityCost, - SCALAR_COST: scalarCost, - OBJECT_COST: objectCost, - LIST_FACTOR: listFactor, -} = process.env - -const name = dbNameFromFile(__filename) -let drop -describe('parse server', () => { - const consoleOutput = [] - const mockedLog = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - beforeAll(async () => { - console.log = mockedLog - console.warn = mockedWarn - // create the database so that middleware can connect - ;({ drop } = await ensure({ - type: 'database', - name, - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - - afterAll(() => drop()) - - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('/alive', () => { - it('returns 200', async () => { - const server = await Server({ - arango: { - db: name, - url, - as: { - username: 'root', - password: rootPass, - }, - }, - }) - - const response = await request(server).get('/alive') - expect(response.status).toEqual(200) - }) - }) - - describe('/ready', () => { - it('returns 200', async () => { - const server = await Server({ - arango: { - db: name, - url, - as: { - username: 'root', - password: rootPass, - }, - }, - }) - - const response = await request(server).get('/ready') - expect(response.status).toEqual(200) - }) - }) - - describe('/graphql', () => { - describe('endpoint is alive', () => { - it('returns 200', async () => { - const response = await request( - await Server({ - arango: { - db: name, - url, - as: { - username: 'root', - password: rootPass, - }, - }, - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, - tracing: false, - context: { - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), - }, - }), - ) - .post('/graphql') - .set('Accept', 'application/json') - .send({ query: '{__schema {types {kind}}}' }) - - expect(response.status).toEqual(200) - }) - }) - describe('validation rule is broken', () => { - describe('query cost is too high', () => { - it('returns an error message', async () => { - const response = await request( - await Server({ - arango: { - db: name, - url, - as: { - username: 'root', - password: rootPass, - }, - }, - maxDepth, - complexityCost: 1, - scalarCost: 100, - objectCost: 100, - listFactor: 100, - context: { - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), - }, - }), - ) - .post('/graphql') - .set('Accept', 'application/json') - .send({ query: '{__schema {types {kind}}}' }) - - expect(response.status).toEqual(400) - expect(response.text).toEqual( - expect.stringContaining('Query error, query is too complex.'), - ) - }) - }) - describe('query depth is too high', () => { - it('returns an error message', async () => { - const response = await request( - await Server({ - arango: { - db: name, - url, - as: { - username: 'root', - password: rootPass, - }, - }, - maxDepth: 1, - complexityCost: 1000, - scalarCost: 1, - objectCost: 1, - listFactor: 1, - context: { - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), - }, - }), - ) - .post('/graphql') - .set('Accept', 'application/json') - .send({ - query: '{findVerifiedDomains (first: 5) { edges { node { id }}}}', - }) - - expect(response.status).toEqual(400) - expect(response.text).toEqual( - expect.stringContaining('exceeds maximum operation depth'), - ) - }) - }) - }) - }) -}) diff --git a/api-js/src/affiliation/inputs/__tests__/affiliation-org-order.test.js b/api-js/src/affiliation/inputs/__tests__/affiliation-org-order.test.js deleted file mode 100644 index d0de8dc68a..0000000000 --- a/api-js/src/affiliation/inputs/__tests__/affiliation-org-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { affiliationOrgOrder } from '../affiliation-org-order' -import { OrderDirection, AffiliationOrgOrderField } from '../../../enums' - -describe('given the affiliationOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = affiliationOrgOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = affiliationOrgOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(AffiliationOrgOrderField), - ) - }) - }) -}) diff --git a/api-js/src/affiliation/inputs/__tests__/affiliation-user-order.test.js b/api-js/src/affiliation/inputs/__tests__/affiliation-user-order.test.js deleted file mode 100644 index bc2d046be7..0000000000 --- a/api-js/src/affiliation/inputs/__tests__/affiliation-user-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { affiliationUserOrder } from '../affiliation-user-order' -import { OrderDirection, AffiliationUserOrderField } from '../../../enums' - -describe('given the affiliationOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = affiliationUserOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = affiliationUserOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(AffiliationUserOrderField), - ) - }) - }) -}) diff --git a/api-js/src/affiliation/loaders/load-affiliation-connections-by-org-id.js b/api-js/src/affiliation/loaders/load-affiliation-connections-by-org-id.js deleted file mode 100644 index d36a129ef2..0000000000 --- a/api-js/src/affiliation/loaders/load-affiliation-connections-by-org-id.js +++ /dev/null @@ -1,300 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadAffiliationConnectionsByOrgId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ orgId, after, before, first, last, orderBy, search }) => { - let afterTemplate = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - let affiliationField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` - documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._to).key).userName` - } - - afterTemplate = aql` - FILTER ${affiliationField} ${afterTemplateDirection} ${documentField} - OR (${affiliationField} == ${documentField} - AND TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - let affiliationField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` - documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._to).key).userName` - } - - beforeTemplate = aql` - FILTER ${affiliationField} ${beforeTemplateDirection} ${documentField} - OR (${affiliationField} == ${documentField} - AND TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Affiliation\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Affiliation\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Affiliation\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Affiliation\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(affiliation._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(affiliation._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let affField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - affField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` - hasNextPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._to).key).userName` - hasPreviousPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._to).key).userName` - } - - hasNextPageFilter = aql` - FILTER ${affField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${affField} == ${hasNextPageDocument} - AND TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${affField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${affField} == ${hasPreviousPageDocument} - AND TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - sortByField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let userSearchQuery = aql`` - let userIdFilter = aql`` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - userSearchQuery = aql` - LET tokenArr = TOKENS(${search}, "text_en") - LET userIds = UNIQUE( - FOR token IN tokenArr - FOR user IN userSearch - SEARCH ANALYZER( - user.displayName LIKE CONCAT("%", token, "%") - OR user.userName LIKE CONCAT("%", token, "%") - , "text_en") - RETURN user._id - ) - ` - userIdFilter = aql`FILTER e._to IN userIds` - } - - let filteredAffiliationCursor - try { - filteredAffiliationCursor = await query` - WITH affiliations, organizations, users, userSearch - - ${userSearchQuery} - - LET affiliationKeys = ( - FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations - ${userIdFilter} - RETURN e._key - ) - - LET retrievedAffiliations = ( - FOR affiliation IN affiliations - FILTER affiliation._key IN affiliationKeys - LET orgKey = PARSE_IDENTIFIER(affiliation._from).key - LET userKey = PARSE_IDENTIFIER(affiliation._to).key - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - id: affiliation._key, - orgKey: orgKey, - userKey: userKey, - _type: "affiliation" - }, - affiliation - ) - ) - - LET hasNextPage = (LENGTH( - FOR affiliation IN affiliations - FILTER affiliation._key IN affiliationKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 - RETURN affiliation - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR affiliation IN affiliations - FILTER affiliation._key IN affiliationKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 - RETURN affiliation - ) > 0 ? true : false) - - RETURN { - "affiliations": retrievedAffiliations, - "totalCount": LENGTH(affiliationKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedAffiliations)._key, - "endKey": LAST(retrievedAffiliations)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to query affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to query affiliation(s). Please try again.`), - ) - } - - let filteredAffiliations - try { - filteredAffiliations = await filteredAffiliationCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load affiliation(s). Please try again.`), - ) - } - - if (filteredAffiliations.affiliations.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = filteredAffiliations.affiliations.map((affiliation) => { - return { - cursor: toGlobalId('affiliation', affiliation._key), - node: affiliation, - } - }) - - return { - edges, - totalCount: filteredAffiliations.totalCount, - pageInfo: { - hasNextPage: filteredAffiliations.hasNextPage, - hasPreviousPage: filteredAffiliations.hasPreviousPage, - startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), - endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), - }, - } - } diff --git a/api-js/src/affiliation/loaders/load-affiliation-connections-by-user-id.js b/api-js/src/affiliation/loaders/load-affiliation-connections-by-user-id.js deleted file mode 100644 index f3b6c86424..0000000000 --- a/api-js/src/affiliation/loaders/load-affiliation-connections-by-user-id.js +++ /dev/null @@ -1,483 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadAffiliationConnectionsByUserId = - ({ query, language, userKey, cleanseInput, i18n }) => - async ({ userId, after, before, first, last, orderBy, search }) => { - let afterTemplate = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - let affiliationField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'org-acronym') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).acronym` - } else if (orderBy.field === 'org-name') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).name` - } else if (orderBy.field === 'org-slug') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).slug` - } else if (orderBy.field === 'org-zone') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).zone` - } else if (orderBy.field === 'org-sector') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).sector` - } else if (orderBy.field === 'org-country') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).country` - } else if (orderBy.field === 'org-province') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).province` - } else if (orderBy.field === 'org-city') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).city` - } else if (orderBy.field === 'org-verified') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).verified` - } else if (orderBy.field === 'org-summary-mail-pass') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.mail.pass` - } else if (orderBy.field === 'org-summary-mail-fail') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.mail.fail` - } else if (orderBy.field === 'org-summary-mail-total') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.mail.total` - } else if (orderBy.field === 'org-summary-web-pass') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.web.pass` - } else if (orderBy.field === 'org-summary-web-fail') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.web.fail` - } else if (orderBy.field === 'org-summary-web-total') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.web.total` - } else if (orderBy.field === 'org-domain-count') { - affiliationField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to)` - documentField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key)._id claims RETURN e._to)` - } - - afterTemplate = aql` - FILTER ${affiliationField} ${afterTemplateDirection} ${documentField} - OR (${affiliationField} == ${documentField} - AND TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - let affiliationField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'org-acronym') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).acronym` - } else if (orderBy.field === 'org-name') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).name` - } else if (orderBy.field === 'org-slug') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).slug` - } else if (orderBy.field === 'org-zone') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).zone` - } else if (orderBy.field === 'org-sector') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).sector` - } else if (orderBy.field === 'org-country') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).country` - } else if (orderBy.field === 'org-province') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).province` - } else if (orderBy.field === 'org-city') { - affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city` - documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).city` - } else if (orderBy.field === 'org-verified') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).verified` - } else if (orderBy.field === 'org-summary-mail-pass') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.mail.pass` - } else if (orderBy.field === 'org-summary-mail-fail') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.mail.fail` - } else if (orderBy.field === 'org-summary-mail-total') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.mail.total` - } else if (orderBy.field === 'org-summary-web-pass') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.web.pass` - } else if (orderBy.field === 'org-summary-web-fail') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.web.fail` - } else if (orderBy.field === 'org-summary-web-total') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.web.total` - } else if (orderBy.field === 'org-domain-count') { - affiliationField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to)` - documentField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key)._id claims RETURN e._to)` - } - - beforeTemplate = aql` - FILTER ${affiliationField} ${beforeTemplateDirection} ${documentField} - OR (${affiliationField} == ${documentField} - AND TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAffiliationConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Affiliation\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAffiliationConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Affiliation\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAffiliationConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Affiliation\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAffiliationConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Affiliation\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(affiliation._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(affiliation._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAffiliationConnectionsByUserId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let affField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'org-acronym') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).acronym` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).acronym` - } else if (orderBy.field === 'org-name') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).name` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).name` - } else if (orderBy.field === 'org-slug') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).slug` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).slug` - } else if (orderBy.field === 'org-zone') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).zone` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).zone` - } else if (orderBy.field === 'org-sector') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).sector` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).sector` - } else if (orderBy.field === 'org-country') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).country` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).country` - } else if (orderBy.field === 'org-province') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).province` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).province` - } else if (orderBy.field === 'org-city') { - affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city` - hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).city` - hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).city` - } else if (orderBy.field === 'org-verified') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).verified` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).verified` - } else if (orderBy.field === 'org-summary-mail-pass') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.mail.pass` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.mail.pass` - } else if (orderBy.field === 'org-summary-mail-fail') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.mail.fail` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.mail.fail` - } else if (orderBy.field === 'org-summary-mail-total') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.mail.total` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.mail.total` - } else if (orderBy.field === 'org-summary-web-pass') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.web.pass` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.web.pass` - } else if (orderBy.field === 'org-summary-web-fail') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.web.fail` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.web.fail` - } else if (orderBy.field === 'org-summary-web-total') { - affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total` - hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.web.total` - hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.web.total` - } else if (orderBy.field === 'org-domain-count') { - affField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to)` - hasNextPageDocument = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key)._id claims RETURN e._to)` - hasPreviousPageDocument = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key)._id claims RETURN e._to)` - } - - hasNextPageFilter = aql` - FILTER ${affField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${affField} == ${hasNextPageDocument} - AND TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${affField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${affField} == ${hasPreviousPageDocument} - AND TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'org-acronym') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym ${orderBy.direction},` - } else if (orderBy.field === 'org-name') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name ${orderBy.direction},` - } else if (orderBy.field === 'org-slug') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug ${orderBy.direction},` - } else if (orderBy.field === 'org-zone') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone ${orderBy.direction},` - } else if (orderBy.field === 'org-sector') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector ${orderBy.direction},` - } else if (orderBy.field === 'org-country') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country ${orderBy.direction},` - } else if (orderBy.field === 'org-province') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province ${orderBy.direction},` - } else if (orderBy.field === 'org-city') { - sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city ${orderBy.direction},` - } else if (orderBy.field === 'org-verified') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified ${orderBy.direction},` - } else if (orderBy.field === 'org-summary-mail-pass') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass ${orderBy.direction},` - } else if (orderBy.field === 'org-summary-mail-fail') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail ${orderBy.direction},` - } else if (orderBy.field === 'org-summary-mail-total') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total ${orderBy.direction},` - } else if (orderBy.field === 'org-summary-web-pass') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass ${orderBy.direction},` - } else if (orderBy.field === 'org-summary-web-fail') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail ${orderBy.direction},` - } else if (orderBy.field === 'org-summary-web-total') { - sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total ${orderBy.direction},` - } else if (orderBy.field === 'org-domain-count') { - sortByField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to) ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let orgSearchQuery = aql`` - let orgIdFilter = aql`` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - orgSearchQuery = aql` - LET tokenArrEN = TOKENS(${search}, "text_en") - LET searchOrgsEN = UNIQUE( - FOR token IN tokenArrEN - FOR org IN organizationSearch - SEARCH ANALYZER( - org.orgDetails.en.acronym LIKE CONCAT("%", token, "%") - OR org.orgDetails.en.name LIKE CONCAT("%", token, "%") - , "text_en") - RETURN org._id - ) - LET tokenArrFR = TOKENS(${search}, "text_fr") - LET searchedOrgsFR = UNIQUE( - FOR token IN tokenArrFR - FOR org IN organizationSearch - SEARCH ANALYZER( - org.orgDetails.fr.acronym LIKE CONCAT("%", token, "%") - OR org.orgDetails.fr.name LIKE CONCAT("%", token, "%") - , "text_fr") - RETURN org._id - ) - LET orgIds = UNION_DISTINCT(searchOrgsEN, searchedOrgsFR) - ` - orgIdFilter = aql`FILTER e._from IN orgIds` - } - - let filteredAffiliationCursor - try { - filteredAffiliationCursor = await query` - WITH affiliations, organizations, organizationSearch users - - ${orgSearchQuery} - - LET affiliationKeys = ( - FOR v, e IN 1..1 INBOUND ${userId} affiliations - ${orgIdFilter} - RETURN e._key - ) - - LET retrievedAffiliations = ( - FOR affiliation IN affiliations - FILTER affiliation._key IN affiliationKeys - LET orgKey = PARSE_IDENTIFIER(affiliation._from).key - LET userKey = PARSE_IDENTIFIER(affiliation._to).key - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE(affiliation, { id: affiliation._key, orgKey: orgKey, userKey: userKey, _type: "affiliation" }) - ) - - LET hasNextPage = (LENGTH( - FOR affiliation IN affiliations - FILTER affiliation._key IN affiliationKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 - RETURN affiliation - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR affiliation IN affiliations - FILTER affiliation._key IN affiliationKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 - RETURN affiliation - ) > 0 ? true : false) - - RETURN { - "affiliations": retrievedAffiliations, - "totalCount": LENGTH(affiliationKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedAffiliations)._key, - "endKey": LAST(retrievedAffiliations)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to query affiliations in loadAffiliationConnectionsByUserId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to query affiliation(s). Please try again.`), - ) - } - - let filteredAffiliations - try { - filteredAffiliations = await filteredAffiliationCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather affiliations in loadAffiliationConnectionsByUserId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load affiliation(s). Please try again.`), - ) - } - - if (filteredAffiliations.affiliations.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = filteredAffiliations.affiliations.map((affiliation) => { - return { - cursor: toGlobalId('affiliation', affiliation._key), - node: affiliation, - } - }) - - return { - edges, - totalCount: filteredAffiliations.totalCount, - pageInfo: { - hasNextPage: filteredAffiliations.hasNextPage, - hasPreviousPage: filteredAffiliations.hasPreviousPage, - startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), - endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), - }, - } - } diff --git a/api-js/src/affiliation/mutations/__tests__/invite-user-to-org.test.js b/api-js/src/affiliation/mutations/__tests__/invite-user-to-org.test.js deleted file mode 100644 index 56079d2a3e..0000000000 --- a/api-js/src/affiliation/mutations/__tests__/invite-user-to-org.test.js +++ /dev/null @@ -1,3269 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' -import { databaseOptions } from '../../../../database-options' -import { createMutationSchema } from '../../../mutation' -import { createQuerySchema } from '../../../query' -import { cleanseInput } from '../../../validators' -import { loadOrgByKey } from '../../../organization/loaders' -import { loadUserByKey, loadUserByUserName } from '../../../user/loaders' - -const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env - -describe('invite user to org', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - tokenize, - user - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(async () => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - tokenize = jest.fn().mockReturnValue('token') - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful invitation', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - tokenize = jest.fn().mockReturnValue('token') - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - let org - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - }) - describe('users role is super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - describe('inviting an existing account', () => { - describe('requested role is super_admin', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - it('returns status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully invited user to organization, and sent notification email.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'english', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Treasury Board of Canada Secretariat', - }) - }) - }) - describe('requested role is admin', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - it('returns status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully invited user to organization, and sent notification email.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'english', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Treasury Board of Canada Secretariat', - }) - }) - }) - describe('requested role is user', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - it('returns status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully invited user to organization, and sent notification email.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'english', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Treasury Board of Canada Secretariat', - }) - }) - }) - }) - describe('inviting a non-existing account', () => { - describe('requested role is super_admin', () => { - it('returns status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully sent invitation to service, and organization email.', - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'super_admin', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { - userName: 'test@email.gc.ca', - preferredLang: 'english', - }, - orgName: 'Treasury Board of Canada Secretariat', - createAccountLink, - }) - }) - }) - describe('requested role is admin', () => { - it('returns status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully sent invitation to service, and organization email.', - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'admin', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { - userName: 'test@email.gc.ca', - preferredLang: 'english', - }, - orgName: 'Treasury Board of Canada Secretariat', - createAccountLink, - }) - }) - }) - describe('requested role is user', () => { - it('returns status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully sent invitation to service, and organization email.', - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'user', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { - userName: 'test@email.gc.ca', - preferredLang: 'english', - }, - orgName: 'Treasury Board of Canada Secretariat', - createAccountLink, - }) - }) - }) - }) - }) - describe('users role is admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - describe('inviting an existing account', () => { - describe('requested role is admin', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - it('returns status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully invited user to organization, and sent notification email.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'english', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Treasury Board of Canada Secretariat', - }) - }) - }) - describe('requested role is user', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - it('returns status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully invited user to organization, and sent notification email.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'english', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Treasury Board of Canada Secretariat', - }) - }) - }) - }) - describe('inviting a non-existing account', () => { - describe('requested role is admin', () => { - it('returns status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully sent invitation to service, and organization email.', - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'admin', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { - userName: 'test@email.gc.ca', - preferredLang: 'english', - }, - orgName: 'Treasury Board of Canada Secretariat', - createAccountLink, - }) - }) - }) - describe('requested role is user', () => { - it('returns status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully sent invitation to service, and organization email.', - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'user', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { - userName: 'test@email.gc.ca', - preferredLang: 'english', - }, - orgName: 'Treasury Board of Canada Secretariat', - createAccountLink, - }) - }) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - let org - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - }) - describe('users role is super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - describe('inviting an existing account', () => { - describe('requested role is super_admin', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'french', - }) - }) - it('returns a status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'french', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - }) - }) - }) - describe('requested role is admin', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'french', - }) - }) - it('returns a status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'french', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - }) - }) - }) - describe('requested role is user', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'french', - }) - }) - it('returns a status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'french', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - }) - }) - }) - }) - describe('inviting a non-existing account', () => { - describe('requested role is super_admin', () => { - it('returns a status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'super_admin', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - createAccountLink, - }) - }) - }) - describe('requested role is admin', () => { - it('returns a status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'admin', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - createAccountLink, - }) - }) - }) - describe('requested role is user', () => { - it('returns a status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'user', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - createAccountLink, - }) - }) - }) - }) - }) - describe('users role is admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - describe('inviting an existing account', () => { - describe('requested role is admin', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'french', - }) - }) - it('returns a status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'french', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - }) - }) - }) - describe('requested role is user', () => { - let secondaryUser - beforeEach(async () => { - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'french', - }) - }) - it('returns a status message', async () => { - const sendOrgInviteEmail = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteEmail).toHaveBeenCalledWith({ - user: { - _type: 'user', - displayName: 'Test Account', - id: secondaryUser._key, - preferredLang: 'french', - userName: 'test@email.gc.ca', - ...secondaryUser, - }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - }) - }) - }) - }) - describe('inviting a non-existing account', () => { - describe('requested role is admin', () => { - it('returns a status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: ADMIN - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'admin', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - createAccountLink, - }) - }) - }) - describe('requested role is user', () => { - it('returns a status message', async () => { - const sendOrgInviteCreateAccount = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - }, - }, - }, - } - - const token = tokenize({ - parameters: { - userName: 'test@email.gc.ca', - orgId: org._id, - requestedRole: 'user', - }, - }) - const createAccountLink = `https://host/create-user/${token}` - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, - ]) - expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', - createAccountLink, - }) - }) - }) - }) - }) - }) - }) - describe('given an unsuccessful invitation', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to invite themselves', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test.account@istio.actually.exists" - requestedRole: USER - orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 400, - description: 'Unable to invite yourself to an org.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite themselves to 1.`, - ]) - }) - }) - describe('user attempts to invite to an org that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 400, - description: 'Unable to invite user to unknown organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to 1 however there is no org associated with that id.`, - ]) - }) - }) - describe('user with undefined permission attempts to invite a user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with user invitations.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: user but does not have permission to do so.`, - ]) - }) - }) - describe('user with user level permission attempts to invite a user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with user invitations.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: user but does not have permission to do so.`, - ]) - }) - }) - describe('user with admin level permission attempts to invite a user to super_admin permission', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with user invitations.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: super_admin but does not have permission to do so.`, - ]) - }) - }) - describe('transaction error occurs', () => { - describe('when creating affiliation', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue('trx step err'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to invite user. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx step err`, - ]) - }) - }) - describe('when committing transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest.fn().mockRejectedValue('trx commit err'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - notify: { - sendOrgInviteCreateAccount: jest.fn(), - sendOrgInviteEmail: jest.fn(), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to invite user. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx commit err`, - ]) - }) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to invite themselves', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test.account@istio.actually.exists" - requestedRole: USER - orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 400, - description: "Impossible de s'inviter à un org.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite themselves to 1.`, - ]) - }) - }) - describe('user attempts to invite to an org that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 400, - description: "Impossible d'inviter un utilisateur à une organisation inconnue.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to 1 however there is no org associated with that id.`, - ]) - }) - }) - describe('user with undefined permission attempts to invite a user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: user but does not have permission to do so.`, - ]) - }) - }) - describe('user with user level permission attempts to invite a user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: user but does not have permission to do so.`, - ]) - }) - }) - describe('user with admin level permission attempts to invite a user to super_admin permission', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: super_admin but does not have permission to do so.`, - ]) - }) - }) - describe('transaction error occurs', () => { - describe('when creating affiliation', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue('trx step err'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible d'inviter un utilisateur. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx step err`, - ]) - }) - }) - describe('when committing transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - inviteUserToOrg( - input: { - userName: "test@email.gc.ca" - requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH - } - ) { - result { - ... on InviteUserToOrgResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest.fn().mockRejectedValue('trx commit err'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - tokenize, - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@exists.ca', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - notify: { - sendOrgInviteCreateAccount: jest.fn(), - sendOrgInviteEmail: jest.fn(), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible d'inviter un utilisateur. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx commit err`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/affiliation/mutations/__tests__/leave-organization.test.js b/api-js/src/affiliation/mutations/__tests__/leave-organization.test.js deleted file mode 100644 index 66126c262b..0000000000 --- a/api-js/src/affiliation/mutations/__tests__/leave-organization.test.js +++ /dev/null @@ -1,3620 +0,0 @@ -import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { checkOrgOwner, userRequired, verifiedRequired } from '../../../auth' -import { loadOrgByKey } from '../../../organization/loaders' -import { loadUserByKey } from '../../../user/loaders' -import { cleanseInput } from '../../../validators' -import { createMutationSchema } from '../../../mutation' -import { createQuerySchema } from '../../../query' - -const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env - -describe('given a successful leave', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - org, - domain, - domain2 - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - domain2 = await collections.domains.save({ - domain: 'test.canada.ca', - slug: 'test-canada-ca', - }) - await collections.claims.save({ - _from: org._id, - _to: domain2._id, - }) - const dkim = await collections.dkim.save({ dkim: true }) - await collections.domainsDKIM.save({ - _from: domain._id, - _to: dkim._id, - }) - const dkimResult = await collections.dkimResults.save({ - dkimResult: true, - }) - await collections.dkimToDkimResults.save({ - _from: dkim._id, - _to: dkimResult._id, - }) - const dmarc = await collections.dmarc.save({ dmarc: true }) - await collections.domainsDMARC.save({ - _from: domain._id, - _to: dmarc._id, - }) - const spf = await collections.spf.save({ spf: true }) - await collections.domainsSPF.save({ - _from: domain._id, - _to: spf._id, - }) - const https = await collections.https.save({ https: true }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, - }) - const ssl = await collections.ssl.save({ ssl: true }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, - }) - const dmarcSummary = await collections.dmarcSummaries.save({ - dmarcSummary: true, - }) - await collections.domainsToDmarcSummaries.save({ - _from: domain._id, - _to: dmarcSummary._id, - }) - }) - afterEach(async () => { - await truncate() - await drop() - consoleOutput.length = 0 - }) - - describe('user is an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: true, - }) - }) - describe('only one org claims the domains', () => { - describe('org is owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - }) - describe('org is not the owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - }) - it('removes all scan data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) - }) - it('removes all domain, affiliation, and org data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - 'Successfully left organization: treasury-board-secretariat', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - }) - describe('multiple orgs claims the domains', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.claims.save({ - _from: org2._id, - _to: domain._id, - }) - }) - describe('org is owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - }) - describe('org is not the owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org2._id, - _to: domain._id, - }) - }) - it('does not remove all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - }) - it('does not remove all scan data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toBeDefined() - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toBeDefined() - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toBeDefined() - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toBeDefined() - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toBeDefined() - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() - }) - it('does not remove all domain', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes affiliation, and org data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testOrgCursor = await query` - FOR org IN organizations - OPTIONS { waitForSync: true } - FILTER org.orgDetails.en.slug != "treasury-board-secretariat-2" - RETURN org - ` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - 'Successfully left organization: treasury-board-secretariat', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - }) - }) - describe('user is not an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: false, - }) - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - it('does not remove scan data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toBeDefined() - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toBeDefined() - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toBeDefined() - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toBeDefined() - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toBeDefined() - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() - }) - it('does not remove org and domain information', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toBeDefined() - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes affiliation data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - 'Successfully left organization: treasury-board-secretariat', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ i18n, query, userKey: user._key }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - }) -}) -describe('given an unsuccessful leave', () => { - let schema, i18n - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(async () => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(async () => { - consoleOutput.length = 0 - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('org cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - code: 400, - description: 'Unable to leave undefined organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User 123 attempted to leave undefined organization: 123`, - ]) - }) - }) - describe('user is an org owner', () => { - describe('database error occurs', () => { - describe('when querying dmarcSummaryCheck', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while checking for dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - describe('when querying domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockReturnValueOnce({ - all: jest.fn().mockReturnValue([]), - }) - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting dmarc summary info', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting dmarc summary info for org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - describe('when getting domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when removing dmarcSummary data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing ownership data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove ownership for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing dkim result data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove dkim results for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove scan results for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing domain', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove domains for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing affiliation, and org data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove affiliations, and the org for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - }) - }) - describe('user is not an org owner', () => { - describe('when removing affiliation information', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - count: 1, - all: jest.fn().mockReturnValue([]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing user: 123 affiliation with org: 123: Error: Step error occurred.`, - ]) - }) - }) - }) - describe('transaction commit error occurs', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ - count: 1, - }), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(new Error('Step error occurred.')), - commit: jest.fn().mockRejectedValue(new Error('Trx Commit Error')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to leave org: 123: Error: Trx Commit Error`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('org cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: { cleanseInput }, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - code: 400, - description: - 'Impossible de quitter une organisation non définie.', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User 123 attempted to leave undefined organization: 123`, - ]) - }) - }) - describe('user is an org owner', () => { - describe('database error occurs', () => { - describe('when querying dmarcSummaryCheck', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while checking for dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - describe('when querying domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockReturnValueOnce({ - all: jest.fn().mockReturnValue([]), - }) - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting dmarc summary info', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting dmarc summary info for org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - describe('when getting domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when removing dmarcSummary data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing ownership data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove ownership for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing dkim result data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove dkim results for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove scan results for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing domain', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove domains for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing affiliation, and org data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove affiliations, and the org for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - }) - }) - describe('user is not an org owner', () => { - describe('when removing affiliation information', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - count: 1, - all: jest.fn().mockReturnValue([]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing user: 123 affiliation with org: 123: Error: Step error occurred.`, - ]) - }) - }) - }) - describe('transaction commit error occurs', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ - count: 1, - }), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(new Error('Step error occurred.')), - commit: jest.fn().mockRejectedValue(new Error('Trx Commit Error')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({ property: 'string' }), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to leave org: 123: Error: Trx Commit Error`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/affiliation/mutations/__tests__/update-user-role.test.js b/api-js/src/affiliation/mutations/__tests__/update-user-role.test.js deleted file mode 100644 index 29175e89cc..0000000000 --- a/api-js/src/affiliation/mutations/__tests__/update-user-role.test.js +++ /dev/null @@ -1,3175 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { cleanseInput } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' -import { loadUserByUserName, loadUserByKey } from '../../../user/loaders' -import { loadOrgByKey } from '../../../organization/loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('update a users role', () => { - let query, drop, truncate, schema, collections, transaction, i18n, user - - let consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(async () => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(() => { - consoleOutput = [] - }) - - describe('given a successful role update', () => { - beforeAll(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - let org, secondaryUser - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - describe('requesting user is a super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - describe('update user from admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: secondaryUser._id, - permission: 'admin', - }) - }) - describe('to super admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: SUPER_ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: 'User role was updated successfully.', - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('to user', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: 'User role was updated successfully.', - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to user in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - describe('update user from user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: secondaryUser._id, - permission: 'user', - }) - }) - describe('to super admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: SUPER_ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: 'User role was updated successfully.', - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('to admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: 'User role was updated successfully.', - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - }) - describe('requesting user is admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - describe('update user from user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: secondaryUser._id, - permission: 'user', - }) - }) - describe('to admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: 'User role was updated successfully.', - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - let org, secondaryUser - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - secondaryUser = await collections.users.save({ - displayName: 'Test Account', - userName: 'test@email.gc.ca', - preferredLang: 'english', - }) - }) - describe('requesting user is a super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - describe('update user from admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: secondaryUser._id, - permission: 'admin', - }) - }) - describe('to super admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: SUPER_ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('to user', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to user in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - describe('update user from user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: secondaryUser._id, - permission: 'user', - }) - }) - describe('to super admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: SUPER_ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('to admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - }) - describe('requesting user is admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - describe('update user from user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: secondaryUser._id, - permission: 'user', - }) - }) - describe('to admin', () => { - it('returns status message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', org._key)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - user { - displayName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - loadUserByUserName: loadUserByUserName({ query }), - }, - validators: { - cleanseInput, - }, - }, - ) - - const expectedResponse = { - data: { - updateUserRole: { - result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", - user: { - displayName: 'Test Account', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - }) - }) - }) - describe('given an unsuccessful update', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given an unsuccessful role update', () => { - describe('user attempts to update their own role', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test.account@istio.actually.exists" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: 'Unable to update your own role.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update their own role in org: 123.`, - ]) - }) - }) - describe('user attempts to update a user that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "random@email.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: 'Unable to update role: user unknown.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: random@email.ca role in org: 123, however there is no user associated with that user name.`, - ]) - }) - }) - describe('user attempts to update a users role in an org that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 1)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: 'Unable to update role: organization unknown.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: 1, however there is no org associated with that id.`, - ]) - }) - }) - describe('requesting user permission is user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with user role changes.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, - ]) - }) - }) - describe('user attempts to update a users role in an org that the requesting user does not belong to', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 0 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with user role changes.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, - ]) - }) - }) - describe('user attempts to update a user that does not belong to the requested org', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 0 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Unable to update role: user does not belong to organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however that user does not have an affiliation with that organization.`, - ]) - }) - }) - describe('requesting users role is super admin', () => { - describe('user attempts to update users role to admin', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: admin.`, - ]) - }) - }) - }) - describe('user attempts to update users role to user', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, - ]) - }) - }) - }) - }) - describe('requesting users role is admin', () => { - describe('requested users role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, - ]) - }) - }) - describe('requested users current role is admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ permission: 'admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from admin to: user.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - describe('when getting current affiliation', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockRejectedValue(new Error('database error')), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when user: 123 attempted to update a user's: 456 role, error: Error: database error`, - ]) - }) - }) - }) - describe('cursor error occur', () => { - describe('when gathering affiliation info', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockRejectedValue(new Error('cursor error')), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when user: 123 attempted to update a user's: 456 role, error: Error: cursor error`, - ]) - }) - }) - }) - describe('transaction error occurs', () => { - describe('when running transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ permission: 'user' }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue('trx step error'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred when user: 123 attempted to update a user's: 456 role, error: trx step error`, - ]) - }) - }) - describe('when committing transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ permission: 'user' }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest.fn().mockRejectedValue('trx commit error'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred when user: 123 attempted to update a user's: 456 role, error: trx commit error`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given an unsuccessful role update', () => { - describe('user attempts to update their own role', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test.account@istio.actually.exists" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Impossible de mettre à jour votre propre rôle.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update their own role in org: 123.`, - ]) - }) - }) - describe('user attempts to update a user that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "random@email.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Impossible de mettre à jour le rôle : utilisateur inconnu.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: random@email.ca role in org: 123, however there is no user associated with that user name.`, - ]) - }) - }) - describe('user attempts to update a users role in an org that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 1)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Impossible de mettre à jour le rôle : organisation inconnue.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: 1, however there is no org associated with that id.`, - ]) - }) - }) - describe('requesting user permission is user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, - ]) - }) - }) - describe('user attempts to update a users role in an org that the requesting user does not belong to', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 0 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, - ]) - }) - }) - describe('user attempts to update a user that does not belong to the requested org', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 0 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however that user does not have an affiliation with that organization.`, - ]) - }) - }) - describe('requesting users role is super admin', () => { - describe('user attempts to update users role to admin', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: admin.`, - ]) - }) - }) - }) - describe('user attempts to update users role to user', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, - ]) - }) - }) - }) - }) - describe('requesting users role is admin', () => { - describe('requested users role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, - ]) - }) - }) - describe('requested users current role is admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ permission: 'admin' }), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from admin to: user.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - describe('when getting current affiliation', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockRejectedValue(new Error('database error')), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when user: 123 attempted to update a user's: 456 role, error: Error: database error`, - ]) - }) - }) - }) - describe('cursor error occur', () => { - describe('when gathering affiliation info', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockRejectedValue(new Error('cursor error')), - }), - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when user: 123 attempted to update a user's: 456 role, error: Error: cursor error`, - ]) - }) - }) - }) - describe('transaction error occurs', () => { - describe('when running transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ permission: 'user' }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue('trx step error'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred when user: 123 attempted to update a user's: 456 role, error: trx step error`, - ]) - }) - }) - describe('when committing transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({ permission: 'user' }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest.fn().mockRejectedValue('trx commit error'), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred when user: 123 attempted to update a user's: 456 role, error: trx commit error`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/affiliation/mutations/index.js b/api-js/src/affiliation/mutations/index.js deleted file mode 100644 index 3b3584b898..0000000000 --- a/api-js/src/affiliation/mutations/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export * from './invite-user-to-org' -export * from './leave-organization' -export * from './remove-user-from-org' -export * from './transfer-org-ownership' -export * from './update-user-role' diff --git a/api-js/src/affiliation/mutations/invite-user-to-org.js b/api-js/src/affiliation/mutations/invite-user-to-org.js deleted file mode 100644 index 832fd1c46a..0000000000 --- a/api-js/src/affiliation/mutations/invite-user-to-org.js +++ /dev/null @@ -1,200 +0,0 @@ -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' -import { t } from '@lingui/macro' - -import { inviteUserToOrgUnion } from '../unions' -import { LanguageEnums, RoleEnums } from '../../enums' - -export const inviteUserToOrg = new mutationWithClientMutationId({ - name: 'InviteUserToOrg', - description: `This mutation allows admins and higher to invite users to any of their -organizations, if the invited user does not have an account, they will be -able to sign-up and be assigned to that organization in one mutation.`, - inputFields: () => ({ - userName: { - type: GraphQLNonNull(GraphQLEmailAddress), - description: 'Users email that you would like to invite to your org.', - }, - requestedRole: { - type: GraphQLNonNull(RoleEnums), - description: 'The role which you would like this user to have.', - }, - orgId: { - type: GraphQLNonNull(GraphQLID), - description: 'The organization you wish to invite the user to.', - }, - preferredLang: { - type: GraphQLNonNull(LanguageEnums), - description: 'The language in which the email will be sent in.', - }, - }), - outputFields: () => ({ - result: { - type: inviteUserToOrgUnion, - description: - '`InviteUserToOrgUnion` returning either a `InviteUserToOrgResult`, or `InviteUserToOrgError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - request, - collections, - transaction, - userKey, - auth: { checkPermission, tokenize, userRequired, verifiedRequired }, - loaders: { loadOrgByKey, loadUserByUserName }, - notify: { sendOrgInviteCreateAccount, sendOrgInviteEmail }, - validators: { cleanseInput }, - }, - ) => { - const userName = cleanseInput(args.userName).toLowerCase() - const requestedRole = cleanseInput(args.requestedRole) - const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - const preferredLang = cleanseInput(args.preferredLang) - - // Get requesting user - const user = await userRequired() - - verifiedRequired({ user }) - - // Make sure user is not inviting themselves - if (user.userName === userName) { - console.warn( - `User: ${userKey} attempted to invite themselves to ${orgId}.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to invite yourself to an org.`), - } - } - - // Check to see if requested org exists - const org = await loadOrgByKey.load(orgId) - - if (typeof org === 'undefined') { - console.warn( - `User: ${userKey} attempted to invite user: ${userName} to ${orgId} however there is no org associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to invite user to unknown organization.`), - } - } - - // Check to see requesting users permission to the org is - const permission = await checkPermission({ orgId: org._id }) - - if ( - typeof permission === 'undefined' || - permission === 'user' || - (permission === 'admin' && requestedRole === 'super_admin') - ) { - console.warn( - `User: ${userKey} attempted to invite user: ${userName} to org: ${org._key} with role: ${requestedRole} but does not have permission to do so.`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with user invitations.`, - ), - } - } - - // Check to see if requested user exists - const requestedUser = await loadUserByUserName.load(userName) - - // If there is not associated account with that user name send invite to org with create account - if (typeof requestedUser === 'undefined') { - const token = tokenize({ - parameters: { userName, orgKey: org._key, requestedRole }, - expPeriod: 24, - }) - const createAccountLink = `https://${request.get( - 'host', - )}/create-user/${token}` - - await sendOrgInviteCreateAccount({ - user: { userName: userName, preferredLang }, - orgName: org.name, - createAccountLink, - }) - - console.info( - `User: ${userKey} successfully invited user: ${userName} to the service, and org: ${org.slug}.`, - ) - - return { - _type: 'regular', - status: i18n._( - t`Successfully sent invitation to service, and organization email.`, - ), - } - } - // If account is found add just add affiliation - else { - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Create affiliation - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - INSERT { - _from: ${org._id}, - _to: ${requestedUser._id}, - permission: ${requestedRole}, - owner: false - } INTO affiliations - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to invite user. Please try again.`)) - } - - await sendOrgInviteEmail({ - user: requestedUser, - orgName: org.name, - }) - - // Commit affiliation - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction commit error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to invite user. Please try again.`)) - } - - console.info( - `User: ${userKey} successfully invited user: ${requestedUser._key} to the org: ${org.slug}.`, - ) - - return { - _type: 'regular', - status: i18n._( - t`Successfully invited user to organization, and sent notification email.`, - ), - } - } - }, -}) diff --git a/api-js/src/affiliation/mutations/leave-organization.js b/api-js/src/affiliation/mutations/leave-organization.js deleted file mode 100644 index 46c8888942..0000000000 --- a/api-js/src/affiliation/mutations/leave-organization.js +++ /dev/null @@ -1,447 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLID, GraphQLNonNull } from 'graphql' -import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' - -import { leaveOrganizationUnion } from '../unions' - -export const leaveOrganization = new mutationWithClientMutationId({ - name: 'LeaveOrganization', - description: 'This mutation allows users to leave a given organization.', - inputFields: () => ({ - orgId: { - type: GraphQLNonNull(GraphQLID), - description: 'Id of the organization the user is looking to leave.', - }, - }), - outputFields: () => ({ - result: { - type: leaveOrganizationUnion, - description: - '`LeaveOrganizationUnion` resolving to either a `LeaveOrganizationResult` or `AffiliationError`.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { checkOrgOwner, userRequired, verifiedRequired }, - loaders: { loadOrgByKey }, - validators: { cleanseInput }, - }, - ) => { - const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) - - const user = await userRequired() - - verifiedRequired({ user }) - - const org = await loadOrgByKey.load(orgKey) - - if (typeof org === 'undefined') { - console.warn( - `User ${user._key} attempted to leave undefined organization: ${orgKey}`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to leave undefined organization.`), - } - } - - // check to see if org owner - const owner = await checkOrgOwner({ orgId: org._id }) - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - if (owner) { - // check to see if org has any dmarc summaries - let dmarcSummaryCheckCursor - try { - dmarcSummaryCheckCursor = await query` - WITH domains, ownership, dmarcSummaries, organizations - FOR v, e IN 1..1 OUTBOUND ${org._id} ownership - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred while checking for dmarc summaries for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - let dmarcSummaryCheckList - try { - dmarcSummaryCheckList = await dmarcSummaryCheckCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting dmarc summary info for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - for (const ownership of dmarcSummaryCheckList) { - try { - await trx.step( - () => query` - WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries - LET dmarcSummaryEdges = ( - FOR v, e IN 1..1 OUTBOUND ${ownership._to} domainsToDmarcSummaries - RETURN { edgeKey: e._key, dmarcSummaryId: e._to } - ) - LET removeDmarcSummaryEdges = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries - OPTIONS { waitForSync: true } - ) - LET removeDmarcSummary = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key - REMOVE key IN dmarcSummaries - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove dmarc summaries for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error( - i18n._(t`Unable leave organization. Please try again.`), - ) - } - - try { - await trx.step( - () => query` - WITH ownership, organizations, domains - REMOVE ${ownership._key} IN ownership - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove ownership for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error( - i18n._(t`Unable leave organization. Please try again.`), - ) - } - } - - // check to see if any other orgs are using this domain - let countCursor - try { - countCursor = await query` - WITH claims, domains, organizations - LET domainIds = ( - FOR v, e IN 1..1 OUTBOUND ${org._id} claims - RETURN e._to - ) - FOR domain IN domains - FILTER domain._id IN domainIds - LET count = LENGTH( - FOR v, e IN 1..1 INBOUND domain._id claims - RETURN 1 - ) - RETURN { - _id: domain._id, - _key: domain._key, - domain: domain.domain, - count - } - ` - } catch (err) { - console.error( - `Database error occurred while while gathering domainInfo org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - let domainInfo - try { - domainInfo = await countCursor.all() - } catch (err) { - console.error( - `Cursor error occurred while while gathering domainInfo org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - for (const domain of domainInfo) { - if (domain.count === 1) { - try { - await trx.step( - () => - query` - WITH claims, dkim, domains, domainsDKIM, organizations, dkimToDkimResults, dkimResults - LET dkimEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsDKIM - RETURN { edgeKey: e._key, dkimId: e._to } - ) - FOR dkimEdge IN dkimEdges - LET dkimResultEdges = ( - FOR v, e IN 1..1 OUTBOUND dkimEdge.dkimId dkimToDkimResults - RETURN { edgeKey: e._key, dkimResultId: e._to } - ) - LET removeDkimResultEdges = ( - FOR dkimResultEdge IN dkimResultEdges - REMOVE dkimResultEdge.edgeKey IN dkimToDkimResults - OPTIONS { waitForSync: true } - ) - LET removeDkimResult = ( - FOR dkimResultEdge IN dkimResultEdges - LET key = PARSE_IDENTIFIER(dkimResultEdge.dkimResultId).key - REMOVE key IN dkimResults - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove dkim results for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error( - i18n._(t`Unable leave organization. Please try again.`), - ) - } - - try { - await Promise.all([ - trx.step( - () => - query` - WITH claims, dkim, domains, domainsDKIM, organizations - LET dkimEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsDKIM - RETURN { edgeKey: e._key, dkimId: e._to } - ) - LET removeDkimEdges = ( - FOR dkimEdge IN dkimEdges - REMOVE dkimEdge.edgeKey IN domainsDKIM - OPTIONS { waitForSync: true } - ) - LET removeDkim = ( - FOR dkimEdge IN dkimEdges - LET key = PARSE_IDENTIFIER(dkimEdge.dkimId).key - REMOVE key IN dkim - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, dmarc, domains, domainsDMARC, organizations - LET dmarcEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsDMARC - RETURN { edgeKey: e._key, dmarcId: e._to } - ) - LET removeDmarcEdges = ( - FOR dmarcEdge IN dmarcEdges - REMOVE dmarcEdge.edgeKey IN domainsDMARC - OPTIONS { waitForSync: true } - ) - LET removeDmarc = ( - FOR dmarcEdge IN dmarcEdges - LET key = PARSE_IDENTIFIER(dmarcEdge.dmarcId).key - REMOVE key IN dmarc - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, domains, domainsSPF, organizations, spf - LET spfEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsSPF - RETURN { edgeKey: e._key, spfId: e._to } - ) - LET removeSpfEdges = ( - FOR spfEdge IN spfEdges - REMOVE spfEdge.edgeKey IN domainsSPF - OPTIONS { waitForSync: true } - ) - LET removeSpf = ( - FOR spfEdge IN spfEdges - LET key = PARSE_IDENTIFIER(spfEdge.spfId).key - REMOVE key IN spf - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, domains, domainsHTTPS, https, organizations - LET httpsEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsHTTPS - RETURN { edgeKey: e._key, httpsId: e._to } - ) - LET removeHttpsEdges = ( - FOR httpsEdge IN httpsEdges - REMOVE httpsEdge.edgeKey IN domainsHTTPS - OPTIONS { waitForSync: true } - ) - LET removeHttps = ( - FOR httpsEdge IN httpsEdges - LET key = PARSE_IDENTIFIER(httpsEdge.httpsId).key - REMOVE key IN https - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, domains, domainsSSL, organizations, ssl - LET sslEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsSSL - RETURN { edgeKey: e._key, sslId: e._to} - ) - LET removeSslEdges = ( - FOR sslEdge IN sslEdges - REMOVE sslEdge.edgeKey IN domainsSSL - OPTIONS { waitForSync: true } - ) - LET removeSsl = ( - FOR sslEdge IN sslEdges - LET key = PARSE_IDENTIFIER(sslEdge.sslId).key - REMOVE key IN ssl - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - ]) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove scan results for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error( - i18n._(t`Unable leave organization. Please try again.`), - ) - } - - try { - await trx.step( - () => - query` - WITH claims, domains, organizations - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${org._id} claims - FILTER e._to == ${domain._id} - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge in domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - LET removeDomain = ( - FOR domainEdge in domainEdges - LET key = PARSE_IDENTIFIER(domainEdge.domainId).key - REMOVE key IN domains - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove domains for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error( - i18n._(t`Unable leave organization. Please try again.`), - ) - } - } - } - - try { - await Promise.all([ - trx.step( - () => - query` - WITH affiliations, organizations, users - LET userEdges = ( - FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations - RETURN { edgeKey: e._key, userKey: e._to } - ) - LET removeUserEdges = ( - FOR userEdge IN userEdges - REMOVE userEdge.edgeKey IN affiliations - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH organizations - REMOVE ${org._key} IN organizations - OPTIONS { waitForSync: true } - `, - ), - ]) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove affiliations, and the org for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - } else { - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations - FILTER e._to == ${user._id} - REMOVE { _key: e._key } IN affiliations - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing user: ${user._key} affiliation with org: ${org._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when user: ${user._key} attempted to leave org: ${org._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - console.info(`User: ${user._key} successfully left org: ${org.slug}.`) - - return { - _type: 'regular', - status: i18n._(t`Successfully left organization: ${org.slug}`), - } - }, -}) diff --git a/api-js/src/affiliation/mutations/remove-user-from-org.js b/api-js/src/affiliation/mutations/remove-user-from-org.js deleted file mode 100644 index 1f13d7f5bd..0000000000 --- a/api-js/src/affiliation/mutations/remove-user-from-org.js +++ /dev/null @@ -1,219 +0,0 @@ -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { removeUserFromOrgUnion } from '../unions' - -export const removeUserFromOrg = new mutationWithClientMutationId({ - name: 'RemoveUserFromOrg', - description: - 'This mutation allows admins or higher to remove users from any organizations they belong to.', - inputFields: () => ({ - userId: { - type: GraphQLNonNull(GraphQLID), - description: 'The user id of the user to be removed.', - }, - orgId: { - type: GraphQLNonNull(GraphQLID), - description: 'The organization that the user is to be removed from.', - }, - }), - outputFields: () => ({ - result: { - type: removeUserFromOrgUnion, - description: - '`RemoveUserFromOrgUnion` returning either a `RemoveUserFromOrgResult`, or `RemoveUserFromOrgError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgByKey, loadUserByKey }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Input - const { id: requestedUserKey } = fromGlobalId(cleanseInput(args.userId)) - const { id: requestedOrgKey } = fromGlobalId(cleanseInput(args.orgId)) - - // Get requesting user - const user = await userRequired() - - verifiedRequired({ user }) - - // Get requested org - const requestedOrg = await loadOrgByKey.load(requestedOrgKey) - if (typeof requestedOrg === 'undefined') { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrgKey}, however no org with that id could be found.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to remove user from unknown organization.`, - ), - } - } - - // Check requesting users permission - const permission = await checkPermission({ orgId: requestedOrg._id }) - if (permission === 'user' || typeof permission === 'undefined') { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrg._key}, however they do not have the permission to remove users.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to remove user from organization.`), - } - } - - // Get requested user - const requestedUser = await loadUserByKey.load(requestedUserKey) - if (typeof requestedUser === 'undefined') { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrg._key}, however no user with that id could be found.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to remove unknown user from organization.`, - ), - } - } - - // Get requested users current permission level - let affiliationCursor - try { - affiliationCursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 ANY ${requestedUser._id} affiliations - FILTER e._from == ${requestedOrg._id} - RETURN { _key: e._key, permission: e.permission } - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} attempted to check the current permission of user: ${requestedUser._key} to see if they could be removed: ${err}`, - ) - throw new Error( - i18n._( - t`Unable to remove user from this organization. Please try again.`, - ), - ) - } - - if (affiliationCursor.count < 1) { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUser._key}, but they do not have any affiliations to org: ${requestedOrg._key}.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to remove a user that already does not belong to this organization.`, - ), - } - } - - let affiliation - try { - affiliation = await affiliationCursor.next() - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} attempted to check the current permission of user: ${requestedUser._key} to see if they could be removed: ${err}`, - ) - throw new Error( - i18n._( - t`Unable to remove user from this organization. Please try again.`, - ), - ) - } - - let canRemove - if ( - permission === 'super_admin' && - (affiliation.permission === 'admin' || affiliation.permission === 'user') - ) { - canRemove = true - } else if (permission === 'admin' && affiliation.permission === 'user') { - canRemove = true - } else { - canRemove = false - } - - if (canRemove) { - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - try { - await trx.step(async () => { - query` - WITH affiliations, organizations, users - FOR aff IN affiliations - FILTER aff._from == ${requestedOrg._id} - FILTER aff._to == ${requestedUser._id} - REMOVE aff IN affiliations - RETURN true - ` - }) - } catch (err) { - console.error( - `Trx step error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove user from this organization. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove user from this organization. Please try again.`), - ) - } - - console.info( - `User: ${userKey} successfully removed user: ${requestedUser._key} from org: ${requestedOrg._key}.`, - ) - - return { - _type: 'regular', - status: i18n._(t`Successfully removed user from organization.`), - user: { - id: requestedUser.id, - userName: requestedUser.userName, - }, - } - } else { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, but they do not have the right permission.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with removing users.`, - ), - } - } - }, -}) diff --git a/api-js/src/affiliation/mutations/transfer-org-ownership.js b/api-js/src/affiliation/mutations/transfer-org-ownership.js deleted file mode 100644 index 3846f9d3e5..0000000000 --- a/api-js/src/affiliation/mutations/transfer-org-ownership.js +++ /dev/null @@ -1,228 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLID, GraphQLNonNull } from 'graphql' -import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' - -import { transferOrgOwnershipUnion } from '../unions' - -export const transferOrgOwnership = new mutationWithClientMutationId({ - name: 'TransferOrgOwnership', - description: - 'This mutation allows a user to transfer org ownership to another user in the given org.', - inputFields: () => ({ - orgId: { - type: GraphQLNonNull(GraphQLID), - description: - 'Id of the organization the user is looking to transfer ownership of.', - }, - userId: { - type: GraphQLNonNull(GraphQLID), - description: - 'Id of the user that the org ownership is being transferred to.', - }, - }), - outputFields: () => ({ - result: { - type: transferOrgOwnershipUnion, - description: - '`TransferOrgOwnershipUnion` resolving to either a `TransferOrgOwnershipResult` or `AffiliationError`.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { checkOrgOwner, userRequired, verifiedRequired }, - loaders: { loadOrgByKey, loadUserByKey }, - validators: { cleanseInput }, - }, - ) => { - // cleanse inputs - const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) - const { id: userTransferKey } = fromGlobalId(cleanseInput(args.userId)) - - // protect mutation from un-authed users - const requestingUser = await userRequired() - - // ensure that user has email verified their account - verifiedRequired({ user: requestingUser }) - - // load the requested org - const org = await loadOrgByKey.load(orgKey) - - // ensure requested org is not undefined - if (typeof org === 'undefined') { - console.warn( - `User: ${requestingUser._key} attempted to transfer org ownership of an undefined org.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to transfer ownership of undefined organization.`, - ), - } - } - // ensure org is not verified - else if (org.verified) { - console.warn( - `User: ${requestingUser._key} attempted to transfer ownership of a verified org: ${org.slug}.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to transfer ownership of a verified organization.`, - ), - } - } - - // get org owner bool value - const owner = await checkOrgOwner({ orgId: org._id }) - - // check to see if requesting user is the org owner - if (!owner) { - console.warn( - `User: ${requestingUser._key} attempted to transfer org: ${org.slug} ownership but does not have current ownership.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact org owner to transfer ownership.`, - ), - } - } - - // get the user that is org ownership is being transferred to - const requestedUser = await loadUserByKey.load(userTransferKey) - - // check to ensure requested user is not undefined - if (typeof requestedUser === 'undefined') { - console.warn( - `User: ${requestingUser._key} attempted to transfer org: ${org.slug} ownership to an undefined user.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to transfer ownership of an org to an undefined user.`, - ), - } - } - - // query db for requested user affiliation to org - let affiliationCursor - try { - affiliationCursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations - FILTER e._to == ${requestedUser._id} - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) - } - - // check to see if requested user belongs to org - if (affiliationCursor.count < 1) { - console.warn( - `User: ${requestingUser._key} attempted to transfer org: ${org.slug} ownership to user: ${requestedUser._key} but they are not affiliated with the org.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to transfer ownership to a user outside the org. Please invite the user and try again.`, - ), - } - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - // remove current org owners role - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - FOR aff IN affiliations - FILTER aff._from == ${org._id} - FILTER aff._to == ${requestingUser._id} - UPDATE { _key: aff._key } WITH { - owner: false, - } IN affiliations - RETURN aff - `, - ) - } catch (err) { - console.error( - `Trx step error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) - } - - // set new org owner - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - FOR aff IN affiliations - FILTER aff._from == ${org._id} - FILTER aff._to == ${requestedUser._id} - UPDATE { _key: aff._key } WITH { - owner: true, - } IN affiliations - RETURN aff - `, - ) - } catch (err) { - console.error( - `Trx step error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) - } - - // commit changes to the db - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) - } - - console.info( - `User: ${requestingUser._key} successfully transfer org: ${org.slug} ownership to user: ${requestedUser._key}.`, - ) - return { - _type: 'regular', - status: i18n._( - t`Successfully transferred org: ${org.slug} ownership to user: ${requestedUser.userName}`, - ), - } - }, -}) diff --git a/api-js/src/affiliation/mutations/update-user-role.js b/api-js/src/affiliation/mutations/update-user-role.js deleted file mode 100644 index 7af6537864..0000000000 --- a/api-js/src/affiliation/mutations/update-user-role.js +++ /dev/null @@ -1,276 +0,0 @@ -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' -import { t } from '@lingui/macro' - -import { RoleEnums } from '../../enums' -import { updateUserRoleUnion } from '../unions' - -export const updateUserRole = new mutationWithClientMutationId({ - name: 'UpdateUserRole', - description: `This mutation allows super admins, and admins of the given organization to -update the permission level of a given user that already belongs to the -given organization.`, - inputFields: () => ({ - userName: { - type: GraphQLNonNull(GraphQLEmailAddress), - description: 'The username of the user you wish to update their role to.', - }, - orgId: { - type: GraphQLNonNull(GraphQLID), - description: - 'The organization that the admin, and the user both belong to.', - }, - role: { - type: GraphQLNonNull(RoleEnums), - description: - 'The role that the admin wants to give to the selected user.', - }, - }), - outputFields: () => ({ - result: { - type: updateUserRoleUnion, - description: - '`UpdateUserRoleUnion` returning either a `UpdateUserRoleResult`, or `UpdateUserRoleError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgByKey, loadUserByUserName }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Input - const userName = cleanseInput(args.userName).toLowerCase() - const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - const role = cleanseInput(args.role) - - // Get requesting user from db - const user = await userRequired() - - verifiedRequired({ user }) - - // Make sure user is not attempting to update their own role - if (user.userName === userName) { - console.warn( - `User: ${userKey} attempted to update their own role in org: ${orgId}.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to update your own role.`), - } - } - - // Check to see if requested user exists - const requestedUser = await loadUserByUserName.load(userName) - - if (typeof requestedUser === 'undefined') { - console.warn( - `User: ${userKey} attempted to update a user: ${userName} role in org: ${orgId}, however there is no user associated with that user name.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to update role: user unknown.`), - } - } - - // Check to see if org exists - const org = await loadOrgByKey.load(orgId) - - if (typeof org === 'undefined') { - console.warn( - `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${orgId}, however there is no org associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to update role: organization unknown.`), - } - } - - // Check requesting user's permission - const permission = await checkPermission({ orgId: org._id }) - - if (permission === 'user' || typeof permission === 'undefined') { - console.warn( - `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to do so.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with user role changes.`, - ), - } - } - - // Get user's current permission level - let affiliationCursor - try { - affiliationCursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 ANY ${requestedUser._id} affiliations - FILTER e._from == ${org._id} - RETURN { _key: e._key, permission: e.permission } - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) - } - - if (affiliationCursor.count < 1) { - console.warn( - `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however that user does not have an affiliation with that organization.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to update role: user does not belong to organization.`, - ), - } - } - - let affiliation - try { - affiliation = await affiliationCursor.next() - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Only super admins can create new super admins - let edge - if (role === 'super_admin' && permission === 'super_admin') { - edge = { - _from: org._id, - _to: requestedUser._id, - permission: 'super_admin', - } - } else if ( - role === 'admin' && - (permission === 'admin' || permission === 'super_admin') - ) { - // If requested user's permission is super admin, make sure they don't get downgraded - if (affiliation.permission === 'super_admin') { - console.warn( - `User: ${userKey} attempted to lower user: ${requestedUser._key} from ${affiliation.permission} to: admin.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), - } - } - - edge = { - _from: org._id, - _to: requestedUser._id, - permission: 'admin', - } - } else if (role === 'user' && permission === 'super_admin') { - // If requested user's permission is super admin or admin, make sure they don't get downgraded - if ( - affiliation.permission === 'super_admin' || - (affiliation.permission === 'admin' && permission !== 'super_admin') - ) { - console.warn( - `User: ${userKey} attempted to lower user: ${requestedUser._key} from ${affiliation.permission} to: user.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), - } - } - - edge = { - _from: org._id, - _to: requestedUser._id, - permission: 'user', - } - } else { - console.warn( - `User: ${userKey} attempted to lower user: ${requestedUser._key} from ${affiliation.permission} to: ${role}.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), - } - } - - try { - await trx.step(async () => { - await query` - WITH affiliations, organizations, users - UPSERT { _key: ${affiliation._key} } - INSERT ${edge} - UPDATE ${edge} - IN affiliations - ` - }) - } catch (err) { - console.error( - `Transaction step error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.warn( - `Transaction commit error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) - } - - console.info( - `User: ${userKey} successful updated user: ${requestedUser._key} role to ${role} in org: ${org.slug}.`, - ) - - return { - _type: 'regular', - status: i18n._(t`User role was updated successfully.`), - user: requestedUser, - } - }, -}) diff --git a/api-js/src/affiliation/objects/__tests__/remove-user-from-org-result.test.js b/api-js/src/affiliation/objects/__tests__/remove-user-from-org-result.test.js deleted file mode 100644 index 66f7d839dd..0000000000 --- a/api-js/src/affiliation/objects/__tests__/remove-user-from-org-result.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import { GraphQLString } from 'graphql' - -import { removeUserFromOrgResultType } from '../remove-user-from-org-result' -import { userSharedType } from '../../../user/objects' - -describe('given the removeUserFromOrgResultType object', () => { - describe('testing the field definitions', () => { - it('has an status field', () => { - const demoType = removeUserFromOrgResultType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(GraphQLString) - }) - it('has a user field', () => { - const demoType = removeUserFromOrgResultType.getFields() - - expect(demoType).toHaveProperty('user') - expect(demoType.user.type).toMatchObject(userSharedType) - }) - }) - describe('testing the field resolvers', () => { - describe('testing the status resolver', () => { - it('returns the resolved field', () => { - const demoType = removeUserFromOrgResultType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the user resolver', () => { - it('returns the resolved field', () => { - const demoType = removeUserFromOrgResultType.getFields() - - expect( - demoType.user.resolve({ user: { id: 1, userName: 'test@email.ca' } }), - ).toEqual({ id: 1, userName: 'test@email.ca' }) - }) - }) - }) -}) diff --git a/api-js/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js b/api-js/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js deleted file mode 100644 index 2111ad4bb6..0000000000 --- a/api-js/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { GraphQLString } from 'graphql' - -import { transferOrgOwnershipResult } from '../transfer-org-ownership-result' - -describe('given the transferOrgOwnershipResult object', () => { - describe('testing the field definitions', () => { - it('has an status field', () => { - const demoType = transferOrgOwnershipResult.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(GraphQLString) - }) - }) - - describe('testing the field resolvers', () => { - describe('testing the status resolver', () => { - it('returns the resolved field', () => { - const demoType = transferOrgOwnershipResult.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - }) -}) diff --git a/api-js/src/affiliation/objects/affiliation-connection.js b/api-js/src/affiliation/objects/affiliation-connection.js deleted file mode 100644 index 8f3b292810..0000000000 --- a/api-js/src/affiliation/objects/affiliation-connection.js +++ /dev/null @@ -1,15 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' -import { affiliationType } from './affiliation' - -export const affiliationConnection = connectionDefinitions({ - name: 'Affiliation', - nodeType: affiliationType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of affiliations the user has access to.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/affiliation/objects/affiliation.js b/api-js/src/affiliation/objects/affiliation.js deleted file mode 100644 index 9490dd2181..0000000000 --- a/api-js/src/affiliation/objects/affiliation.js +++ /dev/null @@ -1,42 +0,0 @@ -import { GraphQLObjectType } from 'graphql' -import { globalIdField } from 'graphql-relay' - -import { RoleEnums } from '../../enums' -import { organizationType } from '../../organization/objects' -import { userSharedType } from '../../user/objects' -import { nodeInterface } from '../../node' - -export const affiliationType = new GraphQLObjectType({ - name: 'Affiliation', - fields: () => ({ - id: globalIdField('affiliation'), - permission: { - type: RoleEnums, - description: "User's level of access to a given organization.", - resolve: ({ permission }) => permission, - }, - user: { - type: userSharedType, - description: 'The affiliated users information.', - resolve: async ({ _to }, _args, { loaders: { loadUserByKey } }) => { - const userKey = _to.split('/')[1] - const user = await loadUserByKey.load(userKey) - user.id = user._key - return user - }, - }, - organization: { - type: organizationType, - description: 'The affiliated organizations information.', - resolve: async ({ _from }, _args, { loaders: { loadOrgByKey } }) => { - const orgKey = _from.split('/')[1] - const org = await loadOrgByKey.load(orgKey) - org.id = org._key - return org - }, - }, - }), - interfaces: [nodeInterface], - description: - 'User Affiliations containing the permission level for the given organization, the users information, and the organizations information.', -}) diff --git a/api-js/src/affiliation/objects/remove-user-from-org-result.js b/api-js/src/affiliation/objects/remove-user-from-org-result.js deleted file mode 100644 index 5934e25e3e..0000000000 --- a/api-js/src/affiliation/objects/remove-user-from-org-result.js +++ /dev/null @@ -1,20 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' - -import { userSharedType } from '../../user/objects' - -export const removeUserFromOrgResultType = new GraphQLObjectType({ - name: 'RemoveUserFromOrgResult', - description: 'This object is used to inform the user of the removal status.', - fields: () => ({ - status: { - type: GraphQLString, - description: 'Informs the user if the user was successfully removed.', - resolve: ({ status }) => status, - }, - user: { - type: userSharedType, - description: 'The user that was just removed.', - resolve: ({ user }) => user, - }, - }), -}) diff --git a/api-js/src/affiliation/objects/update-user-role-result.js b/api-js/src/affiliation/objects/update-user-role-result.js deleted file mode 100644 index bbb29f7c01..0000000000 --- a/api-js/src/affiliation/objects/update-user-role-result.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userSharedType } from '../../user/objects' - -export const updateUserRoleResultType = new GraphQLObjectType({ - name: 'UpdateUserRoleResult', - description: - 'This object is used to inform the user of the status of the role update.', - fields: () => ({ - status: { - type: GraphQLString, - description: - "Informs the user if the user who's role was successfully updated.", - resolve: ({ status }) => status, - }, - user: { - type: userSharedType, - description: "The user who's role was successfully updated.", - resolve: ({ user }) => user, - }, - }), -}) diff --git a/api-js/src/auth/__tests__/check-domain-ownership.test.js b/api-js/src/auth/__tests__/check-domain-ownership.test.js deleted file mode 100644 index 61b0c5680c..0000000000 --- a/api-js/src/auth/__tests__/check-domain-ownership.test.js +++ /dev/null @@ -1,489 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import { databaseOptions } from '../../../database-options' -import { checkDomainOwnership } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the check domain ownership function', () => { - let query, drop, truncate, collections, org, domain, i18n, user - const consoleOutput = [] - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.error = mockedError - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful domain ownership call', () => { - let permitted - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - lastRan: null, - selectors: ['selector1', 'selector2'], - }) - await collections.ownership.save({ - _to: domain._id, - _from: org._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('if the user belongs to an org which has a ownership for a given organization', () => { - afterEach(async () => { - await query` - LET userEdges = (FOR v, e IN 1..1 ANY ${org._id} affiliations RETURN { edgeKey: e._key, userKey: e._to }) - LET removeUserEdges = (FOR userEdge IN userEdges REMOVE userEdge.edgeKey IN affiliations) - RETURN true - ` - await query` - FOR affiliation IN affiliations - REMOVE affiliation IN affiliations - ` - }) - describe('if the user has super-admin-level permissions', () => { - describe('domain has dmarc reports', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - it('will return true', async () => { - const testCheckDomainOwnerShip = checkDomainOwnership({ - query, - userKey: user._key, - }) - permitted = await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - expect(permitted).toEqual(true) - }) - }) - }) - describe('if the user has admin-level permissions', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - it('will return true', async () => { - const testCheckDomainOwnerShip = checkDomainOwnership({ - query, - userKey: user._key, - }) - permitted = await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - expect(permitted).toEqual(true) - }) - }) - describe('if the user has user-level permissions', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - it('will return true', async () => { - const testCheckDomainOwnerShip = checkDomainOwnership({ - query, - userKey: user._key, - }) - permitted = await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - expect(permitted).toEqual(true) - }) - }) - }) - }) - describe('given an unsuccessful domain ownership call', () => { - describe('user is a super admin, but domain does not have any dmarc reports', () => { - describe('domain does not have dmarc reports', () => { - it('will return false', async () => { - const testCheckDomainOwnerShip = checkDomainOwnership({ - query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockReturnValue({ superAdmin: true, domainOwnership: false }), - }), - userKey: 123, - }) - const permitted = await testCheckDomainOwnerShip({ - domainId: 'domains/123', - }) - expect(permitted).toEqual(false) - }) - }) - }) - describe('if the user does not belong to an org which has a ownership for a given domain', () => { - let permitted - it('will return false', async () => { - const testCheckDomainOwnerShip = checkDomainOwnership({ - query: jest - .fn() - .mockReturnValueOnce({ - next: jest.fn().mockReturnValue({ superAdmin: false }), - }).mockReturnValue({ - next: jest.fn().mockReturnValue([]), - }), - userKey: '123', - }) - permitted = await testCheckDomainOwnerShip({ - domainId: 'domains/123', - }) - expect(permitted).toEqual(false) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('if a cursor error is encountered during super admin ownership check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - const firstCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - } - mockQuery = jest.fn().mockReturnValueOnce(firstCursor) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) - expect(consoleOutput).toEqual([ - `Cursor error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, - ]) - } - }) - }) - describe('if a cursor error is encountered during ownership check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - const firstCursor = { - next: jest.fn().mockReturnValue({ - superAdmin: false, - domainOwnership: false, - }), - } - const secondCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - } - mockQuery = jest - .fn() - .mockReturnValueOnce(firstCursor) - .mockReturnValue(secondCursor) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) - expect(consoleOutput).toEqual([ - `Cursor error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, - ]) - } - }) - }) - describe('if a database error is encountered during super admin check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) - expect(consoleOutput).toEqual([ - `Database error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, - ]) - } - }) - }) - describe('if a database error is encountered during ownership check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockReturnValueOnce({ - next() { - return { - superAdmin: false, - domainOwnership: false, - } - }, - }) - .mockRejectedValue(new Error('Database error occurred.')) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) - expect(consoleOutput).toEqual([ - `Database error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, - ]) - } - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('if a cursor error is encountered during super admin ownership check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - const firstCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - } - mockQuery = jest.fn().mockReturnValueOnce(firstCursor) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', - ), - ) - expect(consoleOutput).toEqual([ - `Cursor error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, - ]) - } - }) - }) - describe('if a cursor error is encountered during ownership check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - const firstCursor = { - next: jest.fn().mockReturnValue({ - superAdmin: false, - domainOwnership: false, - }), - } - const secondCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - } - mockQuery = jest - .fn() - .mockReturnValueOnce(firstCursor) - .mockReturnValue(secondCursor) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', - ), - ) - expect(consoleOutput).toEqual([ - `Cursor error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, - ]) - } - }) - }) - describe('if a database error is encountered during super admin check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', - ), - ) - expect(consoleOutput).toEqual([ - `Database error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, - ]) - } - }) - }) - describe('if a database error is encountered during ownership check', () => { - let mockQuery - it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockReturnValueOnce({ - next() { - return { - superAdmin: false, - domainOwnership: false, - } - }, - }) - .mockRejectedValue(new Error('Database error occurred.')) - try { - const testCheckDomainOwnerShip = checkDomainOwnership({ - i18n, - query: mockQuery, - userKey: user._key, - }) - await testCheckDomainOwnerShip({ - domainId: domain._id, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', - ), - ) - expect(consoleOutput).toEqual([ - `Database error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, - ]) - } - }) - }) - }) - }) -}) diff --git a/api-js/src/auth/__tests__/verify-jwt.test.js b/api-js/src/auth/__tests__/verify-jwt.test.js deleted file mode 100644 index 33f2795cb5..0000000000 --- a/api-js/src/auth/__tests__/verify-jwt.test.js +++ /dev/null @@ -1,105 +0,0 @@ -import jwt from 'jsonwebtoken' -import { setupI18n } from '@lingui/core' - -import { verifyToken } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' - -const { AUTHENTICATED_KEY } = process.env - -describe('given a encoded token', () => { - let consoleOutput = [] - let i18n - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - beforeEach(() => { - console.info = mockedInfo - console.warn = mockedWarn - }) - - afterEach(() => { - consoleOutput = [] - }) - describe('token can be decoded and verified', () => { - it('returns the parameters', () => { - const parameters = { - userKey: 1, - } - const token = jwt.sign({ parameters }, String(AUTHENTICATED_KEY), { - algorithm: 'HS256', - }) - - const testVerify = verifyToken({ i18n }) - const decoded = testVerify({ token }) - expect(decoded.userKey).toEqual(1) - }) - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('if the secret does not match', () => { - it('raises an error', () => { - const parameters = { - userKey: 1, - } - const token = jwt.sign({ parameters }, 'superSecretKey', { - algorithm: 'HS256', - }) - - const testVerify = verifyToken({ i18n }) - expect(() => { - testVerify({ token }) - }).toThrow(Error('Invalid token, please sign in.')) - expect(consoleOutput).toEqual([ - `JWT was attempted to be verified but secret was incorrect.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('if the secret does not match', () => { - it('raises an error', () => { - const parameters = { - userKey: 1, - } - const token = jwt.sign({ parameters }, 'superSecretKey', { - algorithm: 'HS256', - }) - - const testVerify = verifyToken({ i18n }) - expect(() => { - testVerify({ token }) - }).toThrow(Error('Jeton invalide, veuillez vous connecter.')) - expect(consoleOutput).toEqual([ - `JWT was attempted to be verified but secret was incorrect.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/auth/check-domain-ownership.js b/api-js/src/auth/check-domain-ownership.js deleted file mode 100644 index 5df1e194bd..0000000000 --- a/api-js/src/auth/check-domain-ownership.js +++ /dev/null @@ -1,84 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkDomainOwnership = - ({ i18n, query, userKey }) => - async ({ domainId }) => { - let userAffiliatedOwnership, ownership - const userKeyString = `users/${userKey}` - - // Check to see if the user is a super admin - let superAdminAffiliationCursor - try { - superAdminAffiliationCursor = await query` - WITH affiliations, organizations, users - LET domainOwnerships = (FOR v, e IN 1..1 ANY ${domainId} ownership RETURN e._from) - LET superAdmin = ( - FOR v, e IN 1..1 ANY ${userKeyString} affiliations - FILTER e.permission == "super_admin" - RETURN e.from - ) - RETURN { - domainOwnership: (LENGTH(domainOwnerships) > 0 ? true : false), - superAdmin: (LENGTH(superAdmin) > 0 ? true : false) - } - ` - } catch (err) { - console.error( - `Database error when retrieving super admin affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) - } - - let superAdminAffiliation - try { - superAdminAffiliation = await superAdminAffiliationCursor.next() - } catch (err) { - console.error( - `Cursor error when retrieving super admin affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) - } - - if (superAdminAffiliation.superAdmin) { - if (superAdminAffiliation.domainOwnership) { - return true - } else { - return false - } - } - - // Get user affiliations and affiliated orgs owning provided domain - try { - userAffiliatedOwnership = await query` - WITH affiliations, domains, organizations, ownership, users - LET userAffiliations = (FOR v, e IN 1..1 ANY ${userKeyString} affiliations RETURN e._from) - LET domainOwnerships = (FOR v, e IN 1..1 ANY ${domainId} ownership RETURN e._from) - LET affiliatedOwnership = INTERSECTION(userAffiliations, domainOwnerships) - RETURN affiliatedOwnership - ` - } catch (err) { - console.error( - `Database error when retrieving affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) - } - - try { - ownership = await userAffiliatedOwnership.next() - } catch (err) { - console.error( - `Cursor error when retrieving affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) - } - - return ownership[0] !== undefined - } diff --git a/api-js/src/auth/check-domain-permission.js b/api-js/src/auth/check-domain-permission.js deleted file mode 100644 index 001df9347f..0000000000 --- a/api-js/src/auth/check-domain-permission.js +++ /dev/null @@ -1,66 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkDomainPermission = - ({ i18n, query, userKey }) => - async ({ domainId }) => { - let userAffiliatedClaims, claim - const userKeyString = `users/${userKey}` - - // Check to see if the user is a super admin - let superAdminAffiliationCursor - try { - superAdminAffiliationCursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 ANY ${userKeyString} affiliations - FILTER e.permission == 'super_admin' - RETURN e.from - ` - } catch (err) { - console.error( - `Database error when retrieving super admin claims for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._( - t`Permission check error. Unable to request domain information.`, - ), - ) - } - - if (superAdminAffiliationCursor.count > 0) { - return true - } - - // Retrieve user affiliations and affiliated organizations owning provided domain - try { - userAffiliatedClaims = await query` - WITH affiliations, claims, domains, organizations, users - LET userAffiliations = (FOR v, e IN 1..1 ANY ${userKeyString} affiliations RETURN e._from) - LET domainClaims = (FOR v, e IN 1..1 ANY ${domainId} claims RETURN e._from) - LET affiliatedClaims = INTERSECTION(userAffiliations, domainClaims) - RETURN affiliatedClaims - ` - } catch (err) { - console.error( - `Database error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._( - t`Permission check error. Unable to request domain information.`, - ), - ) - } - - try { - claim = await userAffiliatedClaims.next() - } catch (err) { - console.error( - `Cursor error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._( - t`Permission check error. Unable to request domain information.`, - ), - ) - } - return claim[0] !== undefined - } diff --git a/api-js/src/auth/check-org-owner.js b/api-js/src/auth/check-org-owner.js deleted file mode 100644 index 647a1de240..0000000000 --- a/api-js/src/auth/check-org-owner.js +++ /dev/null @@ -1,39 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkOrgOwner = - ({ i18n, query, userKey }) => - async ({ orgId }) => { - const userIdString = `users/${userKey}` - - // find affiliation - let affiliationCursor - try { - affiliationCursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations - FILTER e._to == ${userIdString} - RETURN e.owner - ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load owner information. Please try again.`), - ) - } - - let isOrgOwner - try { - isOrgOwner = await affiliationCursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load owner information. Please try again.`), - ) - } - - return isOrgOwner - } diff --git a/api-js/src/auth/check-permission.js b/api-js/src/auth/check-permission.js deleted file mode 100644 index cdba58f1f1..0000000000 --- a/api-js/src/auth/check-permission.js +++ /dev/null @@ -1,63 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkPermission = - ({ i18n, userKey, query }) => - async ({ orgId }) => { - let cursor - const userKeyString = `users/${userKey}` - // Check for super admin - try { - cursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1 INBOUND ${userKeyString} affiliations - FILTER e.permission == "super_admin" - RETURN e.permission - ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`, - ) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } - - let permission - try { - permission = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user ${userKeyString} has super admin permission: ${err}`, - ) - throw new Error(i18n._(t`Unable to check permission. Please try again.`)) - } - - if (permission === 'super_admin') { - return permission - } else { - // Check for other permission level - try { - cursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1 INBOUND ${userKeyString} affiliations - FILTER e._from == ${orgId} - RETURN e.permission - ` - } catch (err) { - console.error( - `Database error occurred when checking ${userKeyString}'s permission: ${err}`, - ) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } - - try { - permission = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking ${userKeyString}'s permission: ${err}`, - ) - throw new Error( - i18n._(t`Unable to check permission. Please try again.`), - ) - } - return permission - } - } diff --git a/api-js/src/auth/check-super-admin.js b/api-js/src/auth/check-super-admin.js deleted file mode 100644 index e97c6a027f..0000000000 --- a/api-js/src/auth/check-super-admin.js +++ /dev/null @@ -1,34 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkSuperAdmin = - ({ i18n, userKey, query }) => - async () => { - let cursor - const userKeyString = `users/${userKey}` - // Check for super admin - try { - cursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1 INBOUND ${userKeyString} affiliations - FILTER e.permission == "super_admin" - RETURN e.permission - ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`, - ) - throw new Error(i18n._(t`Unable to check permission. Please try again.`)) - } - - let permission - try { - permission = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user ${userKeyString} has super admin permission: ${err}`, - ) - throw new Error(i18n._(t`Unable to check permission. Please try again.`)) - } - - return typeof permission !== 'undefined' - } diff --git a/api-js/src/auth/check-user-belongs-to-org.js b/api-js/src/auth/check-user-belongs-to-org.js deleted file mode 100644 index 6909048a8c..0000000000 --- a/api-js/src/auth/check-user-belongs-to-org.js +++ /dev/null @@ -1,27 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkUserBelongsToOrg = - ({ i18n, query, userKey }) => - async ({ orgId }) => { - const userIdString = `users/${userKey}` - - // find affiliation - let affiliationCursor - try { - affiliationCursor = await query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations - FILTER e._to == ${userIdString} - RETURN e - ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKey} belongs to org: ${orgId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load affiliation information. Please try again.`), - ) - } - - return affiliationCursor.count > 0 - } diff --git a/api-js/src/auth/generate-jwt.js b/api-js/src/auth/generate-jwt.js deleted file mode 100644 index 9aa7997311..0000000000 --- a/api-js/src/auth/generate-jwt.js +++ /dev/null @@ -1,25 +0,0 @@ -import jwt from 'jsonwebtoken' - -const { AUTHENTICATED_KEY } = process.env - -const now = () => Math.floor(new Date().getTime() / 1000) - -const future = (expPeriod) => - Math.floor(new Date((now() + expPeriod * 3600) * 1000) / 1000) - -export const tokenize = ({ - parameters = {}, - expPeriod = 1, - iat = now(), - exp = future(expPeriod), - secret = String(AUTHENTICATED_KEY), -}) => - jwt.sign( - { - exp, - iat, - parameters, - }, - secret, - { algorithm: 'HS256' }, - ) diff --git a/api-js/src/auth/index.js b/api-js/src/auth/index.js deleted file mode 100644 index 3a32447d51..0000000000 --- a/api-js/src/auth/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export * from './check-domain-ownership' -export * from './check-domain-permission' -export * from './check-org-owner' -export * from './check-permission' -export * from './check-super-admin' -export * from './check-user-belongs-to-org' -export * from './check-user-is-admin-for-user' -export * from './generate-jwt' -export * from './user-required' -export * from './verified-required' -export * from './verify-jwt' diff --git a/api-js/src/auth/user-required.js b/api-js/src/auth/user-required.js deleted file mode 100644 index 3b46897f85..0000000000 --- a/api-js/src/auth/user-required.js +++ /dev/null @@ -1,32 +0,0 @@ -import { t } from '@lingui/macro' - -export const userRequired = - ({ i18n, userKey, loadUserByKey }) => - async () => { - if (typeof userKey === 'undefined') { - console.warn( - `User attempted to access controlled content, but userKey was undefined.`, - ) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } - - let user, userDoesNotExist - try { - user = await loadUserByKey.load(userKey) - if (typeof user === 'undefined') { - userDoesNotExist = true - } - } catch (err) { - console.error(`Database error occurred when running userRequired: ${err}`) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } - - if (userDoesNotExist) { - console.warn( - `User: ${userKey} attempted to access controlled content, but no user is associated with that id.`, - ) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } - - return user - } diff --git a/api-js/src/auth/verified-required.js b/api-js/src/auth/verified-required.js deleted file mode 100644 index 6d1cbaedb5..0000000000 --- a/api-js/src/auth/verified-required.js +++ /dev/null @@ -1,18 +0,0 @@ -import { t } from '@lingui/macro' - -export const verifiedRequired = - ({ i18n }) => - ({ user }) => { - if (user.emailValidated) { - return true - } - - console.warn( - `User: ${user._key} attempted to access controlled functionality without verification.`, - ) - throw new Error( - i18n._( - t`Verification error. Please verify your account via email to access content.`, - ), - ) - } diff --git a/api-js/src/auth/verify-jwt.js b/api-js/src/auth/verify-jwt.js deleted file mode 100644 index 47afc218b4..0000000000 --- a/api-js/src/auth/verify-jwt.js +++ /dev/null @@ -1,17 +0,0 @@ -import { t } from '@lingui/macro' -import jwt from 'jsonwebtoken' - -const { AUTHENTICATED_KEY } = process.env - -export const verifyToken = - ({ i18n }) => - ({ token, secret = String(AUTHENTICATED_KEY) }) => { - let decoded - try { - decoded = jwt.verify(token, secret) - } catch (err) { - console.warn('JWT was attempted to be verified but secret was incorrect.') - throw new Error(i18n._(t`Invalid token, please sign in.`)) - } - return decoded.parameters - } diff --git a/api-js/src/create-context.js b/api-js/src/create-context.js deleted file mode 100644 index 1f14703de7..0000000000 --- a/api-js/src/create-context.js +++ /dev/null @@ -1,453 +0,0 @@ -import bcrypt from 'bcryptjs' -import moment from 'moment' -import fetch from 'isomorphic-fetch' -import { v4 as uuidv4 } from 'uuid' -import jwt from 'jsonwebtoken' - -import { createI18n } from './create-i18n' -import { cleanseInput, decryptPhoneNumber, slugify } from './validators' -import { - checkDomainOwnership, - checkDomainPermission, - checkOrgOwner, - checkPermission, - checkSuperAdmin, - checkUserBelongsToOrg, - checkUserIsAdminForUser, - tokenize, - userRequired, - verifiedRequired, - verifyToken, -} from './auth' -import { - notifyClient, - sendAuthEmail, - sendAuthTextMsg, - sendOrgInviteCreateAccount, - sendOrgInviteEmail, - sendPasswordResetEmail, - sendTfaTextMsg, - sendVerificationEmail, -} from './notify' - -import { - loadAffiliationByKey, - loadAffiliationConnectionsByUserId, - loadAffiliationConnectionsByOrgId, -} from './affiliation/loaders' -import { - loadDkimFailConnectionsBySumId, - loadDmarcFailConnectionsBySumId, - loadDmarcSummaryConnectionsByUserId, - loadDmarcSummaryEdgeByDomainIdAndPeriod, - loadDmarcSummaryByKey, - loadFullPassConnectionsBySumId, - loadSpfFailureConnectionsBySumId, - loadStartDateFromPeriod, - loadDmarcYearlySumEdge, -} from './dmarc-summaries/loaders' -import { - loadDomainByKey, - loadDomainByDomain, - loadDomainConnectionsByOrgId, - loadDomainConnectionsByUserId, -} from './domain/loaders' -import { - loadDkimByKey, - loadDkimResultByKey, - loadDmarcByKey, - loadSpfByKey, - loadDkimConnectionsByDomainId, - loadDkimResultConnectionsByDkimId, - loadDmarcConnectionsByDomainId, - loadSpfConnectionsByDomainId, -} from './email-scan/loaders' -import { - loadAggregateGuidanceTagByTagId, - loadAggregateGuidanceTagConnectionsByTagId, - loadDkimGuidanceTagByTagId, - loadDkimGuidanceTagConnectionsByTagId, - loadDmarcGuidanceTagByTagId, - loadDmarcGuidanceTagConnectionsByTagId, - loadHttpsGuidanceTagByTagId, - loadHttpsGuidanceTagConnectionsByTagId, - loadSpfGuidanceTagByTagId, - loadSpfGuidanceTagConnectionsByTagId, - loadSslGuidanceTagByTagId, - loadSslGuidanceTagConnectionsByTagId, -} from './guidance-tag/loaders' -import { - loadOrgByKey, - loadOrgBySlug, - loadOrgConnectionsByDomainId, - loadOrgConnectionsByUserId, -} from './organization/loaders' -import { loadUserByUserName, loadUserByKey } from './user/loaders' -import { - loadHttpsByKey, - loadHttpsConnectionsByDomainId, - loadSslByKey, - loadSslConnectionByDomainId, -} from './web-scan/loaders' -import { - loadVerifiedDomainsById, - loadVerifiedDomainByKey, - loadVerifiedDomainConnections, - loadVerifiedDomainConnectionsByOrgId, -} from './verified-domains/loaders' -import { - loadVerifiedOrgByKey, - loadVerifiedOrgBySlug, - loadVerifiedOrgConnectionsByDomainId, - loadVerifiedOrgConnections, -} from './verified-organizations/loaders' -import { loadChartSummaryByKey } from './summaries/loaders' - -export const createContext = - (context) => - async ({ req, res, connection }) => { - if (connection) { - req = { - headers: { - authorization: connection.authorization, - }, - language: connection.language, - } - return createContextObject({ context, req }) - } else { - return createContextObject({ context, req, res }) - } - } - -const createContextObject = ({ context, req: request, res: response }) => { - const { query } = context - - const i18n = createI18n(request.language) - - const verify = verifyToken({ i18n }) - - // Get user id from token - let userKey - const token = request.headers.authorization || '' - if (token !== '') { - userKey = verify({ token }).userKey - } - - return { - ...context, - i18n, - request, - response, - userKey, - moment, - fetch, - uuidv4, - jwt, - auth: { - bcrypt, - checkDomainOwnership: checkDomainOwnership({ i18n, userKey, query }), - checkDomainPermission: checkDomainPermission({ i18n, userKey, query }), - checkOrgOwner: checkOrgOwner({ i18n, userKey, query }), - checkPermission: checkPermission({ i18n, userKey, query }), - checkSuperAdmin: checkSuperAdmin({ i18n, userKey, query }), - checkUserBelongsToOrg: checkUserBelongsToOrg({ i18n, query, userKey }), - checkUserIsAdminForUser: checkUserIsAdminForUser({ - i18n, - userKey, - query, - }), - tokenize, - userRequired: userRequired({ - i18n, - userKey, - loadUserByKey: loadUserByKey({ query, userKey, i18n }), - }), - verifiedRequired: verifiedRequired({ i18n }), - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - decryptPhoneNumber, - slugify, - }, - notify: { - sendAuthEmail: sendAuthEmail({ notifyClient, i18n }), - sendAuthTextMsg: sendAuthTextMsg({ notifyClient, i18n }), - sendOrgInviteCreateAccount: sendOrgInviteCreateAccount({ - notifyClient, - i18n, - }), - sendOrgInviteEmail: sendOrgInviteEmail({ notifyClient, i18n }), - sendPasswordResetEmail: sendPasswordResetEmail({ notifyClient, i18n }), - sendTfaTextMsg: sendTfaTextMsg({ notifyClient, i18n }), - sendVerificationEmail: sendVerificationEmail({ notifyClient, i18n }), - }, - loaders: { - loadChartSummaryByKey: loadChartSummaryByKey({ query, userKey, i18n }), - loadAggregateGuidanceTagByTagId: loadAggregateGuidanceTagByTagId({ - query, - userKey, - i18n, - language: request.language, - }), - loadAggregateGuidanceTagConnectionsByTagId: - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey, - i18n, - cleanseInput, - language: request.language, - }), - loadDkimFailConnectionsBySumId: loadDkimFailConnectionsBySumId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadDmarcFailConnectionsBySumId: loadDmarcFailConnectionsBySumId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId({ - query, - userKey, - cleanseInput, - i18n, - loadStartDateFromPeriod: loadStartDateFromPeriod({ - moment, - userKey, - i18n, - }), - }), - loadDmarcSummaryEdgeByDomainIdAndPeriod: - loadDmarcSummaryEdgeByDomainIdAndPeriod({ - query, - userKey, - i18n, - }), - loadDmarcSummaryByKey: loadDmarcSummaryByKey({ query, userKey, i18n }), - loadFullPassConnectionsBySumId: loadFullPassConnectionsBySumId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadSpfFailureConnectionsBySumId: loadSpfFailureConnectionsBySumId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadStartDateFromPeriod: loadStartDateFromPeriod({ - moment, - userKey, - i18n, - }), - loadDmarcYearlySumEdge: loadDmarcYearlySumEdge({ query, userKey, i18n }), - loadDomainByDomain: loadDomainByDomain({ query, userKey, i18n }), - loadDomainByKey: loadDomainByKey({ query, userKey, i18n }), - loadDomainConnectionsByOrgId: loadDomainConnectionsByOrgId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadDomainConnectionsByUserId: loadDomainConnectionsByUserId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadDkimByKey: loadDkimByKey({ query, userKey, i18n }), - loadDkimResultByKey: loadDkimResultByKey({ query, userKey, i18n }), - loadDmarcByKey: loadDmarcByKey({ query, userKey, i18n }), - loadSpfByKey: loadSpfByKey({ query, userKey, i18n }), - loadDkimConnectionsByDomainId: loadDkimConnectionsByDomainId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadDkimResultConnectionsByDkimId: loadDkimResultConnectionsByDkimId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadDmarcConnectionsByDomainId: loadDmarcConnectionsByDomainId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadSpfConnectionsByDomainId: loadSpfConnectionsByDomainId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadHttpsByKey: loadHttpsByKey({ query, userKey, i18n }), - loadHttpsConnectionsByDomainId: loadHttpsConnectionsByDomainId({ - query, - userKey, - cleanseInput, - }), - loadSslByKey: loadSslByKey({ query, userKey, i18n }), - loadSslConnectionByDomainId: loadSslConnectionByDomainId({ - query, - userKey, - cleanseInput, - }), - loadDkimGuidanceTagByTagId: loadDkimGuidanceTagByTagId({ - query, - userKey, - i18n, - language: request.language, - }), - loadDkimGuidanceTagConnectionsByTagId: - loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language: request.language, - }), - loadDmarcGuidanceTagByTagId: loadDmarcGuidanceTagByTagId({ - query, - userKey, - i18n, - language: request.language, - }), - loadDmarcGuidanceTagConnectionsByTagId: - loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language: request.language, - }), - loadHttpsGuidanceTagByTagId: loadHttpsGuidanceTagByTagId({ - query, - userKey, - i18n, - language: request.language, - }), - loadHttpsGuidanceTagConnectionsByTagId: - loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language: request.language, - }), - loadSpfGuidanceTagByTagId: loadSpfGuidanceTagByTagId({ - query, - userKey, - i18n, - language: request.language, - }), - loadSpfGuidanceTagConnectionsByTagId: - loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language: request.language, - }), - loadSslGuidanceTagByTagId: loadSslGuidanceTagByTagId({ - query, - userKey, - i18n, - language: request.language, - }), - loadSslGuidanceTagConnectionsByTagId: - loadSslGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language: request.language, - }), - loadOrgByKey: loadOrgByKey({ - query, - language: request.language, - userKey, - i18n, - }), - loadOrgBySlug: loadOrgBySlug({ - query, - language: request.language, - userKey, - i18n, - }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: request.language, - userKey, - cleanseInput, - i18n, - }), - loadOrgConnectionsByUserId: loadOrgConnectionsByUserId({ - query, - userKey, - cleanseInput, - language: request.language, - i18n, - }), - loadUserByUserName: loadUserByUserName({ query, userKey, i18n }), - loadUserByKey: loadUserByKey({ query, userKey, i18n }), - loadAffiliationByKey: loadAffiliationByKey({ query, userKey, i18n }), - loadAffiliationConnectionsByUserId: loadAffiliationConnectionsByUserId({ - query, - language: request.language, - userKey, - cleanseInput, - i18n, - }), - loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId({ - query, - userKey, - cleanseInput, - i18n, - }), - loadVerifiedDomainsById: loadVerifiedDomainsById({ query, i18n }), - loadVerifiedDomainByKey: loadVerifiedDomainByKey({ query, i18n }), - loadVerifiedDomainConnections: loadVerifiedDomainConnections({ - query, - cleanseInput, - i18n, - }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), - loadVerifiedOrgByKey: loadVerifiedOrgByKey({ - query, - language: request.language, - i18n, - }), - loadVerifiedOrgBySlug: loadVerifiedOrgBySlug({ - query, - language: request.language, - i18n, - }), - loadVerifiedOrgConnectionsByDomainId: - loadVerifiedOrgConnectionsByDomainId({ - query, - language: request.language, - cleanseInput, - i18n, - }), - loadVerifiedOrgConnections: loadVerifiedOrgConnections({ - query, - language: request.language, - cleanseInput, - i18n, - }), - }, - } -} diff --git a/api-js/src/create-i18n.js b/api-js/src/create-i18n.js deleted file mode 100644 index 0fdc3437fe..0000000000 --- a/api-js/src/create-i18n.js +++ /dev/null @@ -1,18 +0,0 @@ -import { setupI18n } from '@lingui/core' - -import englishMessages from './locale/en/messages' -import frenchMessages from './locale/fr/messages' - -export const createI18n = (language) => - setupI18n({ - locale: language, - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) diff --git a/api-js/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js b/api-js/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js deleted file mode 100644 index a55a132e4e..0000000000 --- a/api-js/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { dmarcSummaryOrder } from '../dmarc-summary-order' -import { OrderDirection, DmarcSummaryOrderField } from '../../../enums' - -describe('given the dmarcSummaryOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = dmarcSummaryOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = dmarcSummaryOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(DmarcSummaryOrderField), - ) - }) - }) -}) diff --git a/api-js/src/dmarc-summaries/loaders/index.js b/api-js/src/dmarc-summaries/loaders/index.js deleted file mode 100644 index a8a6b12b1d..0000000000 --- a/api-js/src/dmarc-summaries/loaders/index.js +++ /dev/null @@ -1,9 +0,0 @@ -export * from './load-dkim-failure-connections-by-sum-id' -export * from './load-dmarc-failure-connections-by-sum-id' -export * from './load-dmarc-sum-connections-by-user-id' -export * from './load-dmarc-sum-edge-by-domain-id-period' -export * from './load-dmarc-summary-by-key' -export * from './load-full-pass-connections-by-sum-id' -export * from './load-spf-failure-connections-by-sum-id' -export * from './load-start-date-from-period' -export * from './load-yearly-dmarc-sum-edges' diff --git a/api-js/src/dmarc-summaries/queries/index.js b/api-js/src/dmarc-summaries/queries/index.js deleted file mode 100644 index 94bbe87479..0000000000 --- a/api-js/src/dmarc-summaries/queries/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './find-my-dmarc-summaries' diff --git a/api-js/src/domain/inputs/__tests__/domain-order.test.js b/api-js/src/domain/inputs/__tests__/domain-order.test.js deleted file mode 100644 index 4ef512e427..0000000000 --- a/api-js/src/domain/inputs/__tests__/domain-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { domainOrder } from '../domain-order' -import { OrderDirection, DomainOrderField } from '../../../enums' - -describe('given the domainOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = domainOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = domainOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(DomainOrderField), - ) - }) - }) -}) diff --git a/api-js/src/domain/inputs/index.js b/api-js/src/domain/inputs/index.js deleted file mode 100644 index 2dc77b3ff7..0000000000 --- a/api-js/src/domain/inputs/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './domain-order' diff --git a/api-js/src/domain/loaders/index.js b/api-js/src/domain/loaders/index.js deleted file mode 100644 index 4bb37015e1..0000000000 --- a/api-js/src/domain/loaders/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './load-domain-by-domain' -export * from './load-domain-by-key' -export * from './load-domain-connections-by-organizations-id' -export * from './load-domain-connections-by-user-id' diff --git a/api-js/src/domain/loaders/load-domain-connections-by-organizations-id.js b/api-js/src/domain/loaders/load-domain-connections-by-organizations-id.js deleted file mode 100644 index e7dde505c1..0000000000 --- a/api-js/src/domain/loaders/load-domain-connections-by-organizations-id.js +++ /dev/null @@ -1,407 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDomainConnectionsByOrgId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ orgId, after, before, first, last, ownership, orderBy, search }) => { - const userDBId = `users/${userKey}` - - let ownershipOrgsOnly = aql` - LET claimKeys = ( - FOR v, e IN 1..1 OUTBOUND ${orgId} claims - OPTIONS {bfs: true} - RETURN v._key - ) - ` - if (typeof ownership !== 'undefined') { - if (ownership) { - ownershipOrgsOnly = aql` - LET claimKeys = ( - FOR v, e IN 1..1 OUTBOUND ${orgId} ownership - OPTIONS {bfs: true} - RETURN v._key - ) - ` - } - } - - let afterTemplate = aql`` - let afterVar = aql`` - - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(domains, ${afterId})` - - let documentField = aql`` - let domainField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - documentField = aql`afterVar.domain` - domainField = aql`domain.domain` - } else if (orderBy.field === 'last-ran') { - documentField = aql`afterVar.lastRan` - domainField = aql`domain.lastRan` - } else if (orderBy.field === 'dkim-status') { - documentField = aql`afterVar.status.dkim` - domainField = aql`domain.status.dkim` - } else if (orderBy.field === 'dmarc-status') { - documentField = aql`afterVar.status.dmarc` - domainField = aql`domain.status.dmarc` - } else if (orderBy.field === 'https-status') { - documentField = aql`afterVar.status.https` - domainField = aql`domain.status.https` - } else if (orderBy.field === 'spf-status') { - documentField = aql`afterVar.status.spf` - domainField = aql`domain.status.spf` - } else if (orderBy.field === 'ssl-status') { - documentField = aql`afterVar.status.ssl` - domainField = aql`domain.status.ssl` - } - - afterTemplate = aql` - FILTER ${domainField} ${afterTemplateDirection} ${documentField} - OR (${domainField} == ${documentField} - AND TO_NUMBER(domain._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(domains, ${beforeId})` - - let documentField = aql`` - let domainField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - documentField = aql`beforeVar.domain` - domainField = aql`domain.domain` - } else if (orderBy.field === 'last-ran') { - documentField = aql`beforeVar.lastRan` - domainField = aql`domain.lastRan` - } else if (orderBy.field === 'dkim-status') { - documentField = aql`beforeVar.status.dkim` - domainField = aql`domain.status.dkim` - } else if (orderBy.field === 'dmarc-status') { - documentField = aql`beforeVar.status.dmarc` - domainField = aql`domain.status.dmarc` - } else if (orderBy.field === 'https-status') { - documentField = aql`beforeVar.status.https` - domainField = aql`domain.status.https` - } else if (orderBy.field === 'spf-status') { - documentField = aql`beforeVar.status.spf` - domainField = aql`domain.status.spf` - } else if (orderBy.field === 'ssl-status') { - documentField = aql`beforeVar.status.ssl` - domainField = aql`domain.status.ssl` - } - - beforeTemplate = aql` - FILTER ${domainField} ${beforeTemplateDirection} ${documentField} - OR (${domainField} == ${documentField} - AND TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDomainConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Domain\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDomainConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDomainConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadDomainConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Domain\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(domain._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(domain._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDomainConnectionsByOrgId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let domainField = aql`` - let hasNextPageDocumentField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - domainField = aql`domain.domain` - hasNextPageDocumentField = aql`LAST(retrievedDomains).domain` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).domain` - } else if (orderBy.field === 'last-ran') { - domainField = aql`domain.lastRan` - hasNextPageDocumentField = aql`LAST(retrievedDomains).lastRan` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).lastRan` - } else if (orderBy.field === 'dkim-status') { - domainField = aql`domain.status.dkim` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dkim` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dkim` - } else if (orderBy.field === 'dmarc-status') { - domainField = aql`domain.status.dmarc` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dmarc` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dmarc` - } else if (orderBy.field === 'https-status') { - domainField = aql`domain.status.https` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.https` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.https` - } else if (orderBy.field === 'spf-status') { - domainField = aql`domain.status.spf` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.spf` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.spf` - } else if (orderBy.field === 'ssl-status') { - domainField = aql`domain.status.ssl` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.ssl` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.ssl` - } - - hasNextPageFilter = aql` - FILTER ${domainField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${domainField} == ${hasNextPageDocumentField} - AND TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)) - ` - hasPreviousPageFilter = aql` - FILTER ${domainField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${domainField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - sortByField = aql`domain.domain ${orderBy.direction},` - } else if (orderBy.field === 'last-ran') { - sortByField = aql`domain.lastRan ${orderBy.direction},` - } else if (orderBy.field === 'dkim-status') { - sortByField = aql`domain.status.dkim ${orderBy.direction},` - } else if (orderBy.field === 'dmarc-status') { - sortByField = aql`domain.status.dmarc ${orderBy.direction},` - } else if (orderBy.field === 'https-status') { - sortByField = aql`domain.status.https ${orderBy.direction},` - } else if (orderBy.field === 'spf-status') { - sortByField = aql`domain.status.spf ${orderBy.direction},` - } else if (orderBy.field === 'ssl-status') { - sortByField = aql`domain.status.ssl ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let domainQuery = aql`` - let loopString = aql`FOR domain IN domains` - let totalCount = aql`LENGTH(domainKeys)` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - domainQuery = aql` - LET tokenArr = TOKENS(${search}, "space-delimiter-analyzer") - LET searchedDomains = ( - FOR tokenItem IN tokenArr - LET token = LOWER(tokenItem) - FOR domain IN domainSearch - SEARCH ANALYZER(domain.domain LIKE CONCAT("%", token, "%"), "space-delimiter-analyzer") - FILTER domain._key IN domainKeys - RETURN MERGE({ id: domain._key, _type: "domain" }, domain) - ) - ` - loopString = aql`FOR domain IN searchedDomains` - totalCount = aql`LENGTH(searchedDomains)` - } - - let requestedDomainInfo - try { - requestedDomainInfo = await query` - WITH affiliations, domains, organizations, users - - LET domainKeys = UNIQUE(FLATTEN( - LET superAdmin = ( - FOR v, e IN 1 INBOUND ${userDBId} affiliations - OPTIONS {bfs: true} - FILTER e.permission == "super_admin" - RETURN e.permission - ) - LET affiliationKeys = ( - FOR v, e IN 1..1 INBOUND ${userDBId} affiliations - OPTIONS {bfs: true} - RETURN v._key - ) - LET superAdminOrgs = (FOR org IN organizations RETURN org._key) - LET keys = ('super_admin' IN superAdmin ? superAdminOrgs : affiliationKeys) - ${ownershipOrgsOnly} - LET orgKeys = INTERSECTION(keys, claimKeys) - RETURN claimKeys - )) - - ${domainQuery} - - ${afterVar} - ${beforeVar} - - LET retrievedDomains = ( - ${loopString} - FILTER domain._key IN domainKeys - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: domain._key, _type: "domain" }, domain) - ) - - LET hasNextPage = (LENGTH( - ${loopString} - FILTER domain._key IN domainKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 - RETURN domain - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - ${loopString} - FILTER domain._key IN domainKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 - RETURN domain - ) > 0 ? true : false) - - RETURN { - "domains": retrievedDomains, - "totalCount": ${totalCount}, - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDomains)._key, - "endKey": LAST(retrievedDomains)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather domains in loadDomainConnectionsByOrgId, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) - } - - let domainsInfo - try { - domainsInfo = await requestedDomainInfo.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather domains in loadDomainConnectionsByOrgId, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) - } - - if (domainsInfo.domains.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = domainsInfo.domains.map((domain) => { - return { - cursor: toGlobalId('domain', domain._key), - node: domain, - } - }) - - return { - edges, - totalCount: domainsInfo.totalCount, - pageInfo: { - hasNextPage: domainsInfo.hasNextPage, - hasPreviousPage: domainsInfo.hasPreviousPage, - startCursor: toGlobalId('domain', domainsInfo.startKey), - endCursor: toGlobalId('domain', domainsInfo.endKey), - }, - } - } diff --git a/api-js/src/domain/loaders/load-domain-connections-by-user-id.js b/api-js/src/domain/loaders/load-domain-connections-by-user-id.js deleted file mode 100644 index 1f36dddb0f..0000000000 --- a/api-js/src/domain/loaders/load-domain-connections-by-user-id.js +++ /dev/null @@ -1,427 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDomainConnectionsByUserId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ - after, - before, - first, - last, - ownership, - orderBy, - isSuperAdmin, - search, - }) => { - const userDBId = `users/${userKey}` - - let ownershipOrgsOnly = aql` - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId claims - OPTIONS {bfs: true} - RETURN v._key - ) - ` - if (typeof ownership !== 'undefined') { - if (ownership) { - ownershipOrgsOnly = aql` - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId ownership - OPTIONS {bfs: true} - RETURN v._key - ) - ` - } - } - - let afterTemplate = aql`` - let afterVar = aql`` - - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(domains, ${afterId})` - - let documentField = aql`` - let domainField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - documentField = aql`afterVar.domain` - domainField = aql`domain.domain` - } else if (orderBy.field === 'last-ran') { - documentField = aql`afterVar.lastRan` - domainField = aql`domain.lastRan` - } else if (orderBy.field === 'dkim-status') { - documentField = aql`afterVar.status.dkim` - domainField = aql`domain.status.dkim` - } else if (orderBy.field === 'dmarc-status') { - documentField = aql`afterVar.status.dmarc` - domainField = aql`domain.status.dmarc` - } else if (orderBy.field === 'https-status') { - documentField = aql`afterVar.status.https` - domainField = aql`domain.status.https` - } else if (orderBy.field === 'spf-status') { - documentField = aql`afterVar.status.spf` - domainField = aql`domain.status.spf` - } else if (orderBy.field === 'ssl-status') { - documentField = aql`afterVar.status.ssl` - domainField = aql`domain.status.ssl` - } - - afterTemplate = aql` - FILTER ${domainField} ${afterTemplateDirection} ${documentField} - OR (${domainField} == ${documentField} - AND TO_NUMBER(domain._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(domains, ${beforeId})` - - let documentField = aql`` - let domainField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - documentField = aql`beforeVar.domain` - domainField = aql`domain.domain` - } else if (orderBy.field === 'last-ran') { - documentField = aql`beforeVar.lastRan` - domainField = aql`domain.lastRan` - } else if (orderBy.field === 'dkim-status') { - documentField = aql`beforeVar.status.dkim` - domainField = aql`domain.status.dkim` - } else if (orderBy.field === 'dmarc-status') { - documentField = aql`beforeVar.status.dmarc` - domainField = aql`domain.status.dmarc` - } else if (orderBy.field === 'https-status') { - documentField = aql`beforeVar.status.https` - domainField = aql`domain.status.https` - } else if (orderBy.field === 'spf-status') { - documentField = aql`beforeVar.status.spf` - domainField = aql`domain.status.spf` - } else if (orderBy.field === 'ssl-status') { - documentField = aql`beforeVar.status.ssl` - domainField = aql`domain.status.ssl` - } - - beforeTemplate = aql` - FILTER ${domainField} ${beforeTemplateDirection} ${documentField} - OR (${domainField} == ${documentField} - AND TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDomainConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Domain\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDomainConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDomainConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDomainConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Domain\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(domain._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(domain._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDomainConnectionsByUserId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let domainField = aql`` - let hasNextPageDocumentField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - domainField = aql`domain.domain` - hasNextPageDocumentField = aql`LAST(retrievedDomains).domain` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).domain` - } else if (orderBy.field === 'last-ran') { - domainField = aql`domain.lastRan` - hasNextPageDocumentField = aql`LAST(retrievedDomains).lastRan` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).lastRan` - } else if (orderBy.field === 'dkim-status') { - domainField = aql`domain.status.dkim` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dkim` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dkim` - } else if (orderBy.field === 'dmarc-status') { - domainField = aql`domain.status.dmarc` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dmarc` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dmarc` - } else if (orderBy.field === 'https-status') { - domainField = aql`domain.status.https` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.https` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.https` - } else if (orderBy.field === 'spf-status') { - domainField = aql`domain.status.spf` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.spf` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.spf` - } else if (orderBy.field === 'ssl-status') { - domainField = aql`domain.status.ssl` - hasNextPageDocumentField = aql`LAST(retrievedDomains).status.ssl` - hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.ssl` - } - - hasNextPageFilter = aql` - FILTER ${domainField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${domainField} == ${hasNextPageDocumentField} - AND TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)) - ` - hasPreviousPageFilter = aql` - FILTER ${domainField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${domainField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'domain') { - sortByField = aql`domain.domain ${orderBy.direction},` - } else if (orderBy.field === 'last-ran') { - sortByField = aql`domain.lastRan ${orderBy.direction},` - } else if (orderBy.field === 'dkim-status') { - sortByField = aql`domain.status.dkim ${orderBy.direction},` - } else if (orderBy.field === 'dmarc-status') { - sortByField = aql`domain.status.dmarc ${orderBy.direction},` - } else if (orderBy.field === 'https-status') { - sortByField = aql`domain.status.https ${orderBy.direction},` - } else if (orderBy.field === 'spf-status') { - sortByField = aql`domain.status.spf ${orderBy.direction},` - } else if (orderBy.field === 'ssl-status') { - sortByField = aql`domain.status.ssl ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let domainKeysQuery - if (isSuperAdmin) { - domainKeysQuery = aql` - WITH affiliations, domains, organizations, users, domainSearch, claims, ownership - LET domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = (FOR org IN organizations RETURN org._id) - FOR orgId IN orgIds - ${ownershipOrgsOnly} - RETURN APPEND(keys, claimDomainKeys) - )) - ` - } else { - domainKeysQuery = aql` - WITH affiliations, domains, organizations, users, domainSearch, claims, ownership - LET domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = ( - FOR v, e IN 1..1 ANY ${userDBId} affiliations - OPTIONS {bfs: true} - RETURN e._from - ) - FOR orgId IN orgIds - ${ownershipOrgsOnly} - RETURN APPEND(keys, claimDomainKeys) - )) - - ` - } - - let domainQuery = aql`` - let loopString = aql`FOR domain IN domains` - let totalCount = aql`LENGTH(domainKeys)` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - domainQuery = aql` - LET tokenArr = TOKENS(${search}, "space-delimiter-analyzer") - LET searchedDomains = ( - FOR tokenItem in tokenArr - LET token = LOWER(tokenItem) - FOR domain IN domainSearch - SEARCH ANALYZER(domain.domain LIKE CONCAT("%", token, "%"), "space-delimiter-analyzer") - FILTER domain._key IN domainKeys - RETURN domain - ) - ` - loopString = aql`FOR domain IN searchedDomains` - totalCount = aql`LENGTH(searchedDomains)` - } - - let requestedDomainInfo - try { - requestedDomainInfo = await query` - ${domainKeysQuery} - - ${domainQuery} - - ${afterVar} - ${beforeVar} - - LET retrievedDomains = ( - ${loopString} - FILTER domain._key IN domainKeys - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: domain._key, _type: "domain" }, domain) - ) - - LET hasNextPage = (LENGTH( - ${loopString} - FILTER domain._key IN domainKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 - RETURN domain - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - ${loopString} - FILTER domain._key IN domainKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 - RETURN domain - ) > 0 ? true : false) - - RETURN { - "domains": retrievedDomains, - "totalCount": ${totalCount}, - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDomains)._key, - "endKey": LAST(retrievedDomains)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to query domains in loadDomainsByUser, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to query domain(s). Please try again.`)) - } - - let domainsInfo - try { - domainsInfo = await requestedDomainInfo.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather domains in loadDomainsByUser, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) - } - - if (domainsInfo.domains.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = domainsInfo.domains.map((domain) => { - return { - cursor: toGlobalId('domain', domain._key), - node: domain, - } - }) - - return { - edges, - totalCount: domainsInfo.totalCount, - pageInfo: { - hasNextPage: domainsInfo.hasNextPage, - hasPreviousPage: domainsInfo.hasPreviousPage, - startCursor: toGlobalId('domain', domainsInfo.startKey), - endCursor: toGlobalId('domain', domainsInfo.endKey), - }, - } - } diff --git a/api-js/src/domain/mutations/__tests__/create-domain.test.js b/api-js/src/domain/mutations/__tests__/create-domain.test.js deleted file mode 100644 index 03572f2dbc..0000000000 --- a/api-js/src/domain/mutations/__tests__/create-domain.test.js +++ /dev/null @@ -1,3520 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { cleanseInput, slugify } from '../../../validators' -import { - checkPermission, - userRequired, - checkSuperAdmin, - verifiedRequired, -} from '../../../auth' -import { loadDomainByDomain } from '../../loaders' -import { - loadOrgByKey, - loadOrgConnectionsByDomainId, -} from '../../../organization/loaders' -import { loadUserByKey } from '../../../user/loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('create a domain', () => { - let query, drop, truncate, schema, collections, transaction, user, org - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(async () => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - describe('given a successful domain creation', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('user has super admin permission level', () => { - describe('user belongs to the same org', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('user belongs to a different org', () => { - let secondOrg - beforeEach(async () => { - secondOrg = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'communications-security-establishment', - acronym: 'CSE', - name: 'Communications Security Establishment', - zone: 'FED', - sector: 'DND', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'centre-de-la-securite-des-telecommunications', - acronym: 'CST', - name: 'Centre de la Securite des Telecommunications', - zone: 'FED', - sector: 'DND', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: secondOrg._id, - _to: user._id, - permission: 'super_admin', - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, - ]) - }) - }) - }) - describe('user has admin permission level', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('user has user permission level', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('domain can be created in a different organization', () => { - let secondOrg - beforeEach(async () => { - secondOrg = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'communications-security-establishment', - acronym: 'CSE', - name: 'Communications Security Establishment', - zone: 'FED', - sector: 'DND', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'centre-de-la-securite-des-telecommunications', - acronym: 'CST', - name: 'Centre de la Securite des Telecommunications', - zone: 'FED', - sector: 'DND', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: secondOrg._id, - _to: user._id, - permission: 'super_admin', - }) - }) - describe('selectors are not added', () => { - beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - lastRan: null, - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', secondOrg._key)}" - domain: "test.gc.ca" - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - { - node: { - id: toGlobalId('organization', secondOrg._key), - name: 'Communications Security Establishment', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, - ]) - }) - }) - describe('selectors are the same', () => { - beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - lastRan: null, - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', secondOrg._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - { - node: { - id: toGlobalId('organization', secondOrg._key), - name: 'Communications Security Establishment', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, - ]) - }) - }) - describe('new selectors are added', () => { - beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - lastRan: null, - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', secondOrg._key)}" - domain: "test.gc.ca" - selectors: ["selector3._domainkey", "selector4._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: [ - 'selector1._domainkey', - 'selector2._domainkey', - 'selector3._domainkey', - 'selector4._domainkey', - ], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - { - node: { - id: toGlobalId('organization', secondOrg._key), - name: 'Communications Security Establishment', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, - ]) - }) - }) - describe('lastRan is not changed', () => { - beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - lastRan: '2021-01-01 12:00:00.000000', - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', secondOrg._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: '2021-01-01 12:00:00.000000', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - { - node: { - id: toGlobalId('organization', secondOrg._key), - name: 'Communications Security Establishment', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, - ]) - }) - }) - describe('status do not changed', () => { - beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - lastRan: '', - status: { - dkim: 'fail', - dmarc: 'fail', - https: 'fail', - spf: 'fail', - ssl: 'fail', - }, - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', secondOrg._key)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ - query, - language: 'en', - userKey: user._key, - cleanseInput, - }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { cleanseInput, slugify }, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: '', - selectors: ['selector1._domainkey', 'selector2._domainkey'], - status: { - dkim: 'FAIL', - dmarc: 'FAIL', - https: 'FAIL', - spf: 'FAIL', - ssl: 'FAIL', - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - { - node: { - id: toGlobalId('organization', secondOrg._key), - name: 'Communications Security Establishment', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, - ]) - }) - }) - }) - }) - describe('given an unsuccessful domain creation', () => { - let i18n - describe('request language is english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('org does not exist', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "b3JnYW5pemF0aW9uOjE=" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - 'Unable to create domain in unknown organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain to an organization: 1 that does not exist.`, - ]) - }) - }) - describe('user does not belong to organization', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization user for help with creating domain.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain in: treasury-board-secretariat, however they do not have permission to do so.`, - ]) - }) - }) - describe('the domain already exists in the given organization', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({}), - }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - 'Unable to create domain, organization has already claimed it.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain for: treasury-board-secretariat, however that org already has that domain claimed.`, - ]) - }) - }) - describe('database error occurs', () => { - describe('when checking to see if org already contains domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while running check to see if domain already exists in an org: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when checking to see if org already contains domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while running check to see if domain already exists in an org: Error: Cursor error occurred.`, - ]) - }) - }) - describe('when gathering inserted domain', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValueOnce({ - next: jest - .fn() - .mockRejectedValue(new Error('cursor error')), - }), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred for user: 123 after inserting new domain and gathering its domain info: Error: cursor error`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when creating a new domain', () => { - describe('when inserting new domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting new domain: Error: trx step error`, - ]) - }) - }) - describe('when inserting new edge', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ - next: jest.fn(), - }) - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting new domain edge: Error: trx step error`, - ]) - }) - }) - }) - describe('when domain already exists', () => { - describe('when upserting domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn().mockReturnValue({ - domain: 'domain.ca', - selectors: [], - status: {}, - lastRan: '', - }), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting domain selectors: Error: trx step error`, - ]) - }) - }) - describe('when inserting edge to new org', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn().mockReturnValue({ - domain: 'domain.ca', - selectors: [], - status: {}, - lastRan: '', - }), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting domain edge: Error: trx step error`, - ]) - }) - }) - }) - }) - describe('transaction commit error occurs', () => { - describe('when committing transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn().mockReturnValue({ - domain: 'domain.ca', - selectors: [], - status: {}, - lastRan: '', - }), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred while user: 123 was creating domain: Error: trx commit error`, - ]) - }) - }) - }) - }) - describe('request language is french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('org does not exist', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "b3JnYW5pemF0aW9uOjE=" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - 'Impossible de créer un domaine dans une organisation inconnue.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain to an organization: 1 that does not exist.`, - ]) - }) - }) - describe('user does not belong to organization', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain in: treasury-board-secretariat, however they do not have permission to do so.`, - ]) - }) - }) - describe('the domain already exists in the given organization', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({}), - }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - "Impossible de créer le domaine, l'organisation l'a déjà réclamé.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain for: treasury-board-secretariat, however that org already has that domain claimed.`, - ]) - }) - }) - describe('database error occurs', () => { - describe('when checking to see if org already contains domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while running check to see if domain already exists in an org: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when checking to see if org already contains domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while running check to see if domain already exists in an org: Error: Cursor error occurred.`, - ]) - }) - }) - describe('when gathering inserted domain', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValueOnce({ - next: jest - .fn() - .mockRejectedValue(new Error('cursor error')), - }), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred for user: 123 after inserting new domain and gathering its domain info: Error: cursor error`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when creating a new domain', () => { - describe('when inserting new domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting new domain: Error: trx step error`, - ]) - }) - }) - describe('when inserting new edge', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ - next: jest.fn(), - }) - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting new domain edge: Error: trx step error`, - ]) - }) - }) - }) - describe('when domain already exists', () => { - describe('when upserting domain', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn().mockReturnValue({ - domain: 'domain.ca', - selectors: [], - status: {}, - lastRan: '', - }), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting domain selectors: Error: trx step error`, - ]) - }) - }) - describe('when inserting edge to new org', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn().mockReturnValue({ - domain: 'domain.ca', - selectors: [], - status: {}, - lastRan: '', - }), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred for user: 123 when inserting domain edge: Error: trx step error`, - ]) - }) - }) - }) - }) - describe('transaction commit error occurs', () => { - describe('when committing transaction', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', 123)}" - domain: "test.gc.ca" - selectors: ["selector1._domainkey", "selector2._domainkey"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - loaders: { - loadDomainByDomain: { - load: jest.fn().mockReturnValue({ - domain: 'domain.ca', - selectors: [], - status: {}, - lastRan: '', - }), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, - }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred while user: 123 was creating domain: Error: trx commit error`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/domain/mutations/__tests__/request-scan.test.js b/api-js/src/domain/mutations/__tests__/request-scan.test.js deleted file mode 100644 index 5b918eebe8..0000000000 --- a/api-js/src/domain/mutations/__tests__/request-scan.test.js +++ /dev/null @@ -1,1366 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { setupI18n } from '@lingui/core' -import { v4 as uuidv4 } from 'uuid' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { - checkDomainPermission, - userRequired, - verifiedRequired, -} from '../../../auth' -import { loadDomainByDomain } from '../../loaders' -import { loadUserByKey } from '../../../user/loaders' -import { cleanseInput } from '../../../validators' - -require('jest-fetch-mock').enableFetchMocks() - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('requesting a one time scan', () => { - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - - let query, drop, truncate, schema, collections, i18n, org, user, domain, org2 - - beforeAll(async () => { - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - lastRan: null, - selectors: ['selector1', 'selector2'], - }) - await collections.claims.save({ - _to: domain._id, - _from: org._id, - }) - }) - afterEach(async () => { - consoleOutput.length = 0 - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a successful request', () => { - let mockUUID - beforeEach(() => { - fetch.mockResponseOnce(JSON.stringify({ data: '12345' })) - mockUUID = jest.fn().mockReturnValue('uuid-token-1234') - }) - describe('user is a super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: 'organizations/SA', - _to: user._id, - permission: 'super_admin', - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4: mockUUID, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - requestScan: { - status: 'Successfully dispatched one time scan.', - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully dispatched a one time scan on domain: test.gc.ca.`, - ]) - }) - }) - describe('user is an org admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4: mockUUID, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - requestScan: { - status: 'Successfully dispatched one time scan.', - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully dispatched a one time scan on domain: test.gc.ca.`, - ]) - }) - }) - describe('user is an org user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4: mockUUID, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - requestScan: { - status: 'Successfully dispatched one time scan.', - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully dispatched a one time scan on domain: test.gc.ca.`, - ]) - }) - }) - }) - describe('given an unsuccessful request', () => { - describe('domain cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test-domain.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to request a one time scan on an unknown domain.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to step a one time scan on: test-domain.gc.ca however domain cannot be found.`, - ]) - }) - }) - describe('user does not have domain permission', () => { - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - }) - describe('user is admin to another org', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'admin', - }) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Permission Denied: Please contact organization user for help with scanning this domain.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to step a one time scan on: test.gc.ca however they do not have permission to do so.`, - ]) - }) - }) - describe('user is a user in another org', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'user', - }) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Permission Denied: Please contact organization user for help with scanning this domain.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to step a one time scan on: test.gc.ca however they do not have permission to do so.`, - ]) - }) - }) - }) - describe('fetch error occurs', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - describe('when sending dns scan request', () => { - beforeEach(() => { - const fetch = fetchMock - fetch.mockRejectOnce(Error('Fetch Error occurred.')) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to dispatch one time scan. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Fetch error when dispatching dns scan for user: ${user._key}, on domain: test.gc.ca, error: Error: Fetch Error occurred.`, - ]) - }) - }) - describe('when sending https scan request', () => { - beforeEach(() => { - const fetch = fetchMock - fetch - .mockResponseOnce(JSON.stringify({ data: '12345' })) - .mockRejectOnce(Error('Fetch Error occurred.')) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to dispatch one time scan. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Fetch error when dispatching https scan for user: ${user._key}, on domain: test.gc.ca, error: Error: Fetch Error occurred.`, - ]) - }) - }) - describe('when sending ssl scan request', () => { - beforeEach(() => { - const fetch = fetchMock - fetch - .mockResponseOnce(JSON.stringify({ data: '12345' })) - .mockResponseOnce(JSON.stringify({ data: '12345' })) - .mockRejectOnce(Error('Fetch Error occurred.')) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to dispatch one time scan. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Fetch error when dispatching ssl scan for user: ${user._key}, on domain: test.gc.ca, error: Error: Fetch Error occurred.`, - ]) - }) - }) - }) - }) - }) - describe('users langue is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a successful request', () => { - let mockUUID - beforeEach(() => { - const fetch = fetchMock - fetch.mockResponseOnce(JSON.stringify({ data: '12345' })) - mockUUID = jest.fn().mockReturnValue('uuid-token-1234') - }) - describe('user is a super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: 'organizations/SA', - _to: user._id, - permission: 'super_admin', - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4: mockUUID, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - requestScan: { - status: 'Un seul balayage a été effectué avec succès.', - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully dispatched a one time scan on domain: test.gc.ca.`, - ]) - }) - }) - describe('user is an org admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4: mockUUID, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - requestScan: { - status: 'Un seul balayage a été effectué avec succès.', - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully dispatched a one time scan on domain: test.gc.ca.`, - ]) - }) - }) - describe('user is an org user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4: mockUUID, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - requestScan: { - status: 'Un seul balayage a été effectué avec succès.', - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully dispatched a one time scan on domain: test.gc.ca.`, - ]) - }) - }) - }) - describe('given an unsuccessful request', () => { - describe('domain cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test-domain.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de demander un scan unique sur un domaine inconnu.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to step a one time scan on: test-domain.gc.ca however domain cannot be found.`, - ]) - }) - }) - describe('user does not have domain permission', () => { - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - }) - describe('user is admin to another org', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'admin', - }) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to step a one time scan on: test.gc.ca however they do not have permission to do so.`, - ]) - }) - }) - describe('user is a user in another org', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'user', - }) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to step a one time scan on: test.gc.ca however they do not have permission to do so.`, - ]) - }) - }) - }) - describe('fetch error occurs', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - describe('when sending dns scan request', () => { - beforeEach(() => { - const fetch = fetchMock - fetch.mockRejectOnce(Error('Fetch Error occurred.')) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible d'envoyer un scan unique. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Fetch error when dispatching dns scan for user: ${user._key}, on domain: test.gc.ca, error: Error: Fetch Error occurred.`, - ]) - }) - }) - describe('when sending https scan request', () => { - beforeEach(() => { - const fetch = fetchMock - fetch - .mockResponseOnce(JSON.stringify({ data: '12345' })) - .mockRejectOnce(Error('Fetch Error occurred.')) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible d'envoyer un scan unique. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Fetch error when dispatching https scan for user: ${user._key}, on domain: test.gc.ca, error: Error: Fetch Error occurred.`, - ]) - }) - }) - describe('when sending ssl scan request', () => { - beforeEach(() => { - const fetch = fetchMock - fetch - .mockResponseOnce(JSON.stringify({ data: '12345' })) - .mockResponseOnce(JSON.stringify({ data: '12345' })) - .mockRejectOnce(Error('Fetch Error occurred.')) - }) - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - requestScan(input: { domain: "test.gc.ca" }) { - status - } - } - `, - null, - { - i18n, - fetch, - userKey: user._key, - uuidv4, - auth: { - checkDomainPermission: checkDomainPermission({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({ i18n }), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible d'envoyer un scan unique. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Fetch error when dispatching ssl scan for user: ${user._key}, on domain: test.gc.ca, error: Error: Fetch Error occurred.`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/domain/mutations/__tests__/update-domain.test.js b/api-js/src/domain/mutations/__tests__/update-domain.test.js deleted file mode 100644 index 594ea21f4e..0000000000 --- a/api-js/src/domain/mutations/__tests__/update-domain.test.js +++ /dev/null @@ -1,1876 +0,0 @@ -import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { cleanseInput, slugify } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' -import { loadDomainByKey } from '../../loaders' -import { loadOrgByKey } from '../../../organization/loaders' -import { loadUserByKey } from '../../../user/loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('updating a domain', () => { - let query, drop, truncate, schema, collections, transaction, user - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful domain update', () => { - let org, domain - beforeAll(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - }) - await collections.claims.save({ - _to: domain._id, - _from: org._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users permission is super admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _to: user._id, - _from: org._id, - permission: 'super_admin', - }) - }) - describe('user updates domain', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.canada.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - describe('user updates selectors', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector3._domainkey', 'selector4._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - describe('user updates domain and selectors', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.canada.ca', - lastRan: null, - selectors: ['selector3._domainkey', 'selector4._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - }) - describe('users permission is admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _to: user._id, - _from: org._id, - permission: 'admin', - }) - }) - describe('user updates domain', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.canada.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - describe('user updates selectors', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector3._domainkey', 'selector4._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - describe('user updates domain and selectors', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.canada.ca', - lastRan: null, - selectors: ['selector3._domainkey', 'selector4._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - }) - describe('users permission is user', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _to: user._id, - _from: org._id, - permission: 'admin', - }) - }) - describe('user updates domain', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.canada.ca', - lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - describe('user updates selectors', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector3._domainkey', 'selector4._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - describe('user updates domain and selectors', () => { - it('returns updated domain', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', domain._key)}" - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: loadDomainByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.canada.ca', - lastRan: null, - selectors: ['selector3._domainkey', 'selector4._domainkey'], - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) - }) - }) - }) - }) - describe('given an unsuccessful domain update', () => { - let i18n - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('domain cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 1)}" - orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 400, - description: 'Unable to update unknown domain.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 1, however there is no domain associated with that id.`, - ]) - }) - }) - describe('organization cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 400, - description: 'Unable to update domain in an unknown org.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 123 for org: 1, however there is no org associated with that id.`, - ]) - }) - }) - describe('user does not belong to org', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization user for help with updating this domain.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 123 for org: 123, however they do not have permission in that org.`, - ]) - }) - }) - describe('domain and org do not have any edges', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 0 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 400, - description: - 'Unable to update domain that does not belong to the given organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 123 for org: 123, however that org has no claims to that domain.`, - ]) - }) - }) - describe('database error occurs', () => { - describe('while checking for edge connections', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockRejectedValue(new Error('database error')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while user: 123 attempted to update domain: 123, error: Error: database error`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when running domain upsert', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred when user: 123 attempted to update domain: 123, error: Error: trx step error`, - ]) - }) - }) - }) - describe('transaction commit error occurs', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred when user: 123 attempted to update domain: 123, error: Error: trx commit error`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('domain cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 1)}" - orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 400, - description: - 'Impossible de mettre à jour un domaine inconnu.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 1, however there is no domain associated with that id.`, - ]) - }) - }) - describe('organization cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 400, - description: - 'Impossible de mettre à jour le domaine dans un org inconnu.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 123 for org: 1, however there is no org associated with that id.`, - ]) - }) - }) - describe('user does not belong to org', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 403, - description: - "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 123 for org: 123, however they do not have permission in that org.`, - ]) - }) - }) - describe('domain and org do not have any edges', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 0 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = { - data: { - updateDomain: { - result: { - code: 400, - description: - "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update domain: 123 for org: 123, however that org has no claims to that domain.`, - ]) - }) - }) - describe('database error occurs', () => { - describe('while checking for edge connections', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockRejectedValue(new Error('database error')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while user: 123 attempted to update domain: 123, error: Error: database error`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when running domain upsert', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction step error occurred when user: 123 attempted to update domain: 123, error: Error: trx step error`, - ]) - }) - }) - }) - describe('transaction commit error occurs', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateDomain ( - input: { - domainId: "${toGlobalId('domain', 123)}" - orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3._domainkey", - "selector4._domainkey" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { load: jest.fn() }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction commit error occurred when user: 123 attempted to update domain: 123, error: Error: trx commit error`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/domain/mutations/create-domain.js b/api-js/src/domain/mutations/create-domain.js deleted file mode 100644 index 8d8413739a..0000000000 --- a/api-js/src/domain/mutations/create-domain.js +++ /dev/null @@ -1,290 +0,0 @@ -import { GraphQLNonNull, GraphQLList, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { createDomainUnion } from '../unions' -import { Domain, Selectors } from '../../scalars' - -export const createDomain = new mutationWithClientMutationId({ - name: 'CreateDomain', - description: 'Mutation used to create a new domain for an organization.', - inputFields: () => ({ - orgId: { - type: GraphQLNonNull(GraphQLID), - description: - 'The global id of the organization you wish to assign this domain to.', - }, - domain: { - type: GraphQLNonNull(Domain), - description: 'Url that you would like to be added to the database.', - }, - selectors: { - type: new GraphQLList(Selectors), - description: 'DKIM selector strings corresponding to this domain.', - }, - }), - outputFields: () => ({ - result: { - type: createDomainUnion, - description: - '`CreateDomainUnion` returning either a `Domain`, or `CreateDomainError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - request, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadDomainByDomain, loadOrgByKey }, - validators: { cleanseInput }, - }, - ) => { - // Get User - const user = await userRequired() - - verifiedRequired({ user }) - - // Cleanse input - const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - const domain = cleanseInput(args.domain) - - let selectors - if (typeof args.selectors !== 'undefined') { - selectors = args.selectors.map((selector) => cleanseInput(selector)) - } else { - selectors = [] - } - - // Check to see if org exists - const org = await loadOrgByKey.load(orgId) - - if (typeof org === 'undefined') { - console.warn( - `User: ${userKey} attempted to create a domain to an organization: ${orgId} that does not exist.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to create domain in unknown organization.`, - ), - } - } - - // Check to see if user belongs to org - const permission = await checkPermission({ orgId: org._id }) - - if ( - permission !== 'user' && - permission !== 'admin' && - permission !== 'super_admin' - ) { - console.warn( - `User: ${userKey} attempted to create a domain in: ${org.slug}, however they do not have permission to do so.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization user for help with creating domain.`, - ), - } - } - - const insertDomain = { - domain: domain.toLowerCase(), - lastRan: null, - selectors: selectors, - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - } - - // Check to see if domain already belongs to same org - let checkDomainCursor - try { - checkDomainCursor = await query` - WITH claims, domains, organizations - LET domainIds = (FOR domain IN domains FILTER domain.domain == ${insertDomain.domain} RETURN { id: domain._id }) - FOR domainId IN domainIds - LET domainEdges = (FOR v, e IN 1..1 ANY domainId.id claims RETURN { _from: e._from }) - FOR domainEdge IN domainEdges - LET org = DOCUMENT(domainEdge._from) - FILTER org._key == ${org._key} - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev }, TRANSLATE(${request.language}, org.orgDetails)) - ` - } catch (err) { - console.error( - `Database error occurred while running check to see if domain already exists in an org: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - - let checkOrgDomain - try { - checkOrgDomain = await checkDomainCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while running check to see if domain already exists in an org: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - - if (typeof checkOrgDomain !== 'undefined') { - console.warn( - `User: ${userKey} attempted to create a domain for: ${org.slug}, however that org already has that domain claimed.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to create domain, organization has already claimed it.`, - ), - } - } - - // Check to see if domain already exists in db - const checkDomain = await loadDomainByDomain.load(insertDomain.domain) - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - let insertedDomainCursor - if (typeof checkDomain === 'undefined') { - try { - insertedDomainCursor = await trx.step( - () => - query` - WITH domains - INSERT ${insertDomain} INTO domains - RETURN MERGE( - { - id: NEW._key, - _type: "domain" - }, - NEW - ) - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred for user: ${userKey} when inserting new domain: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - - let insertedDomain - try { - insertedDomain = await insertedDomainCursor.next() - } catch (err) { - console.error( - `Cursor error occurred for user: ${userKey} after inserting new domain and gathering its domain info: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - - try { - await trx.step( - () => - query` - WITH claims, domains, organizations - INSERT { - _from: ${org._id}, - _to: ${insertedDomain._id} - } INTO claims - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred for user: ${userKey} when inserting new domain edge: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - } else { - const { selectors: selectorList, status, lastRan } = checkDomain - - selectors.forEach((selector) => { - if (!checkDomain.selectors.includes(selector)) { - selectorList.push(selector) - } - }) - - insertDomain.selectors = selectorList - insertDomain.status = status - insertDomain.lastRan = lastRan - - try { - await trx.step( - () => - query` - WITH claims, domains, organizations - UPSERT { _key: ${checkDomain._key} } - INSERT ${insertDomain} - UPDATE ${insertDomain} - IN domains - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred for user: ${userKey} when inserting domain selectors: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - - try { - await trx.step( - () => - query` - WITH claims, domains, organizations - INSERT { - _from: ${org._id}, - _to: ${checkDomain._id} - } INTO claims - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred for user: ${userKey} when inserting domain edge: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction commit error occurred while user: ${userKey} was creating domain: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } - - // Clear dataloader incase anything was updated or inserted into domain - await loadDomainByDomain.clear(insertDomain.domain) - const returnDomain = await loadDomainByDomain.load(insertDomain.domain) - - console.info( - `User: ${userKey} successfully created ${returnDomain.domain} in org: ${org.slug}.`, - ) - - return { - ...returnDomain, - } - }, -}) diff --git a/api-js/src/domain/mutations/index.js b/api-js/src/domain/mutations/index.js deleted file mode 100644 index e873b67aff..0000000000 --- a/api-js/src/domain/mutations/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './create-domain' -export * from './remove-domain' -export * from './request-scan' -export * from './update-domain' diff --git a/api-js/src/domain/mutations/remove-domain.js b/api-js/src/domain/mutations/remove-domain.js deleted file mode 100644 index 23a28b9772..0000000000 --- a/api-js/src/domain/mutations/remove-domain.js +++ /dev/null @@ -1,407 +0,0 @@ -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { removeDomainUnion } from '../unions' - -export const removeDomain = new mutationWithClientMutationId({ - name: 'RemoveDomain', - description: 'This mutation allows the removal of unused domains.', - inputFields: () => ({ - domainId: { - type: GraphQLNonNull(GraphQLID), - description: 'The global id of the domain you wish to remove.', - }, - orgId: { - type: GraphQLNonNull(GraphQLID), - description: 'The organization you wish to remove the domain from.', - }, - }), - outputFields: () => ({ - result: { - type: GraphQLNonNull(removeDomainUnion), - description: - '`RemoveDomainUnion` returning either a `DomainResultType`, or `DomainErrorType` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - validators: { cleanseInput }, - loaders: { loadDomainByKey, loadOrgByKey }, - }, - ) => { - // Get User - const user = await userRequired() - - verifiedRequired({ user }) - - // Cleanse Input - const { type: _domainType, id: domainId } = fromGlobalId( - cleanseInput(args.domainId), - ) - const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - - // Get domain from db - const domain = await loadDomainByKey.load(domainId) - - // Check to see if domain exists - if (typeof domain === 'undefined') { - console.warn( - `User: ${userKey} attempted to remove ${domainId} however no domain is associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to remove unknown domain.`), - } - } - - // Get Org from db - const org = await loadOrgByKey.load(orgId) - - // Check to see if org exists - if (typeof org === 'undefined') { - console.warn( - `User: ${userKey} attempted to remove ${domain.slug} in org: ${orgId} however there is no organization associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to remove domain from unknown organization.`, - ), - } - } - - // Get permission - const permission = await checkPermission({ orgId: org._id }) - - // Check to see if domain belongs to verified check org - if (org.verified && permission !== 'super_admin') { - console.warn( - `User: ${userKey} attempted to remove ${domain.slug} in ${org.slug} but does not have permission to remove a domain from a verified check org.`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact super admin for help with removing domain.`, - ), - } - } - - if (permission !== 'super_admin' && permission !== 'admin') { - console.warn( - `User: ${userKey} attempted to remove ${domain.slug} in ${org.slug} however they do not have permission in that org.`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with removing domain.`, - ), - } - } - - // Check to see if more than one organization has a claim to this domain - let countCursor - try { - countCursor = await query` - WITH claims, domains, organizations - FOR v, e IN 1..1 ANY ${domain._id} claims RETURN true - ` - } catch (err) { - console.error( - `Database error occurred for user: ${userKey}, when counting domain claims for domain: ${domain.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - - // check to see if org removing domain has ownership - let dmarcCountCursor - try { - dmarcCountCursor = await query` - WITH domains, organizations, ownership - FOR v IN 1..1 OUTBOUND ${org._id} ownership RETURN true - ` - } catch (err) { - console.error( - `Database error occurred for user: ${userKey}, when counting ownership claims for domain: ${domain.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - if (dmarcCountCursor.count === 1) { - try { - await trx.step( - () => query` - WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries - LET dmarcSummaryEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsToDmarcSummaries - RETURN { edgeKey: e._key, dmarcSummaryId: e._to } - ) - LET removeDmarcSummaryEdges = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries - OPTIONS { waitForSync: true } - ) - LET removeDmarcSummary = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key - REMOVE key IN dmarcSummaries - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing dmarc summary data for user: ${userKey} while attempting to remove domain: ${domain.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - - try { - await trx.step( - () => query` - WITH ownership, organizations, domains - LET domainEdges = ( - FOR v, e IN 1..1 INBOUND ${domain._id} ownership - REMOVE e._key IN ownership - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing ownership data for user: ${userKey} while attempting to remove domain: ${domain.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - } - - if (countCursor.count <= 1) { - // Remove scan data - try { - await Promise.all([ - trx.step(async () => { - await query` - WITH claims, dkim, domains, domainsDKIM, organizations, dkimToDkimResults, dkimResults - LET domainEdges = (FOR v, e IN 1..1 OUTBOUND ${org._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - FOR domainEdge in domainEdges - LET dkimEdges = (FOR v, e IN 1..1 OUTBOUND domainEdge.domainId domainsDKIM RETURN { edgeKey: e._key, dkimId: e._to }) - FOR dkimEdge IN dkimEdges - LET dkimResultEdges = (FOR v, e IN 1..1 OUTBOUND dkimEdge.dkimId dkimToDkimResults RETURN { edgeKey: e._key, dkimResultId: e._to }) - LET removeDkimResultEdges = ( - FOR dkimResultEdge IN dkimResultEdges - REMOVE dkimResultEdge.edgeKey IN dkimToDkimResults - OPTIONS { waitForSync: true } - ) - LET removeDkimResult = ( - FOR dkimResultEdge IN dkimResultEdges - LET key = PARSE_IDENTIFIER(dkimResultEdge.dkimResultId).key - REMOVE key IN dkimResults - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }), - trx.step(async () => { - await query` - WITH claims, dkim, domains, domainsDKIM, organizations - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - FOR domainEdge in domainEdges - LET dkimEdges = (FOR v, e IN 1..1 OUTBOUND domainEdge.domainId domainsDKIM RETURN { edgeKey: e._key, dkimId: e._to }) - LET removeDkimEdges = ( - FOR dkimEdge IN dkimEdges - REMOVE dkimEdge.edgeKey IN domainsDKIM - OPTIONS { waitForSync: true } - ) - LET removeDkim = ( - FOR dkimEdge IN dkimEdges - LET key = PARSE_IDENTIFIER(dkimEdge.dkimId).key - REMOVE key IN dkim - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }), - trx.step(async () => { - await query` - WITH claims, dmarc, domains, domainsDMARC, organizations - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - FOR domainEdge in domainEdges - LET dmarcEdges = (FOR v, e IN 1..1 OUTBOUND domainEdge.domainId domainsDMARC RETURN { edgeKey: e._key, dmarcId: e._to }) - LET removeDmarcEdges = ( - FOR dmarcEdge IN dmarcEdges - REMOVE dmarcEdge.edgeKey IN domainsDMARC - OPTIONS { waitForSync: true } - ) - LET removeDmarc = ( - FOR dmarcEdge IN dmarcEdges - LET key = PARSE_IDENTIFIER(dmarcEdge.dmarcId).key - REMOVE key IN dmarc - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }), - trx.step(async () => { - await query` - WITH claims, domains, domainsSPF, organizations, spf - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - FOR domainEdge in domainEdges - LET spfEdges = (FOR v, e IN 1..1 OUTBOUND domainEdge.domainId domainsSPF RETURN { edgeKey: e._key, spfId: e._to }) - LET removeSpfEdges = ( - FOR spfEdge IN spfEdges - REMOVE spfEdge.edgeKey IN domainsSPF - OPTIONS { waitForSync: true } - ) - LET removeSpf = ( - FOR spfEdge IN spfEdges - LET key = PARSE_IDENTIFIER(spfEdge.spfId).key - REMOVE key IN spf - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }), - trx.step(async () => { - await query` - WITH claims, domains, domainsHTTPS, https, organizations - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - FOR domainEdge in domainEdges - LET httpsEdges = (FOR v, e IN 1..1 OUTBOUND domainEdge.domainId domainsHTTPS RETURN { edgeKey: e._key, httpsId: e._to }) - LET removeHttpsEdges = ( - FOR httpsEdge IN httpsEdges - REMOVE httpsEdge.edgeKey IN domainsHTTPS - OPTIONS { waitForSync: true } - ) - LET removeHttps = ( - FOR httpsEdge IN httpsEdges - LET key = PARSE_IDENTIFIER(httpsEdge.httpsId).key - REMOVE key IN https - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }), - trx.step(async () => { - await query` - WITH claims, domains, domainsSSL, organizations, ssl - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - FOR domainEdge in domainEdges - LET sslEdges = (FOR v, e IN 1..1 OUTBOUND domainEdge.domainId domainsSSL RETURN { edgeKey: e._key, sslId: e._to}) - LET removeSslEdges = ( - FOR sslEdge IN sslEdges - REMOVE sslEdge.edgeKey IN domainsSSL - OPTIONS { waitForSync: true } - ) - LET removeSsl = ( - FOR sslEdge IN sslEdges - LET key = PARSE_IDENTIFIER(sslEdge.sslId).key - REMOVE key IN ssl - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }), - ]) - } catch (err) { - console.error( - `Trx step error occurred while user: ${userKey} attempted to remove scan data for ${domain.slug} in org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - - // Remove domain - try { - await trx.step(async () => { - await query` - WITH claims, domains, organizations - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { edgeKey: e._key, domainId: e._to }) - LET removeDomainEdges = ( - FOR domainEdge in domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - LET removeDomain = ( - FOR domainEdge in domainEdges - LET key = PARSE_IDENTIFIER(domainEdge.domainId).key - REMOVE key IN domains - OPTIONS { waitForSync: true } - ) - RETURN true - ` - }) - } catch (err) { - console.error( - `Trx step error occurred while user: ${userKey} attempted to remove ${domain.slug} in org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - } else { - try { - await trx.step(async () => { - await query` - WITH claims, domains, organizations - LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { _key: e._key, _from: e._from, _to: e._to }) - LET edgeKeys = ( - FOR domainEdge IN domainEdges - FILTER domainEdge._to == ${domain._id} - FILTER domainEdge._from == ${org._id} - RETURN domainEdge._key - ) - FOR edgeKey IN edgeKeys - REMOVE edgeKey IN claims - OPTIONS { waitForSync: true } - ` - }) - } catch (err) { - console.error( - `Trx step error occurred while user: ${userKey} attempted to remove claim for ${domain.slug} in org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - } - - // Commit transaction - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${userKey} attempted to remove ${domain.slug} in org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) - } - - console.info( - `User: ${userKey} successfully removed domain: ${domain.slug} from org: ${org.slug}.`, - ) - return { - _type: 'result', - status: i18n._( - t`Successfully removed domain: ${domain.slug} from ${org.slug}.`, - ), - domain, - } - }, -}) diff --git a/api-js/src/domain/mutations/request-scan.js b/api-js/src/domain/mutations/request-scan.js deleted file mode 100644 index 3c49fdf239..0000000000 --- a/api-js/src/domain/mutations/request-scan.js +++ /dev/null @@ -1,134 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { v4 as uuidv4 } from 'uuid' - -import { Domain } from '../../scalars' - -const { - DNS_SCANNER_ENDPOINT, - HTTPS_SCANNER_ENDPOINT, - SSL_SCANNER_ENDPOINT, -} = process.env - -export const requestScan = new mutationWithClientMutationId({ - name: 'RequestScan', - description: - 'This mutation is used to step a manual scan on a requested domain.', - inputFields: () => ({ - domain: { - type: Domain, - description: 'The domain that the scan will be ran on.', - }, - }), - outputFields: () => ({ - status: { - type: GraphQLString, - description: 'Informs the user if the scan was dispatched successfully.', - resolve: ({ status }) => status, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - fetch, - userKey, - auth: { checkDomainPermission, userRequired, verifiedRequired }, - loaders: { loadDomainByDomain }, - validators: { cleanseInput }, - }, - ) => { - // User is required - const user = await userRequired() - - verifiedRequired({ user }) - - const requestedDomain = cleanseInput(args.domain) - - // Check to see if domain exists - const domain = await loadDomainByDomain.load(requestedDomain) - - if (typeof domain === 'undefined') { - console.warn( - `User: ${userKey} attempted to step a one time scan on: ${requestedDomain} however domain cannot be found.`, - ) - throw new Error( - i18n._(t`Unable to request a one time scan on an unknown domain.`), - ) - } - - // Check to see if user has access to domain - const permission = await checkDomainPermission({ domainId: domain._id }) - - if (!permission) { - console.warn( - `User: ${userKey} attempted to step a one time scan on: ${domain.domain} however they do not have permission to do so.`, - ) - throw new Error( - i18n._( - t`Permission Denied: Please contact organization user for help with scanning this domain.`, - ), - ) - } - - const sharedId = uuidv4() - - const parameters = { - domain_key: domain._key, - domain: domain.domain, - selectors: domain.selectors, - user_key: userKey, - shared_id: sharedId, - } - - try { - await fetch(DNS_SCANNER_ENDPOINT, { - method: 'POST', - body: JSON.stringify(parameters), - }) - } catch (err) { - console.error( - `Fetch error when dispatching dns scan for user: ${userKey}, on domain: ${domain.domain}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to dispatch one time scan. Please try again.`), - ) - } - - try { - await fetch(HTTPS_SCANNER_ENDPOINT, { - method: 'POST', - body: JSON.stringify(parameters), - }) - } catch (err) { - console.error( - `Fetch error when dispatching https scan for user: ${userKey}, on domain: ${domain.domain}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to dispatch one time scan. Please try again.`), - ) - } - - try { - await fetch(SSL_SCANNER_ENDPOINT, { - method: 'POST', - body: JSON.stringify(parameters), - }) - } catch (err) { - console.error( - `Fetch error when dispatching ssl scan for user: ${userKey}, on domain: ${domain.domain}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to dispatch one time scan. Please try again.`), - ) - } - - console.info( - `User: ${userKey} successfully dispatched a one time scan on domain: ${domain.domain}.`, - ) - return { - status: i18n._(t`Successfully dispatched one time scan.`), - } - }, -}) diff --git a/api-js/src/domain/mutations/update-domain.js b/api-js/src/domain/mutations/update-domain.js deleted file mode 100644 index 1546224a65..0000000000 --- a/api-js/src/domain/mutations/update-domain.js +++ /dev/null @@ -1,199 +0,0 @@ -import { GraphQLID, GraphQLNonNull, GraphQLList } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { updateDomainUnion } from '../unions' -import { Domain, Selectors } from '../../scalars' - -export const updateDomain = new mutationWithClientMutationId({ - name: 'UpdateDomain', - description: - 'Mutation allows the modification of domains if domain is updated through out its life-cycle', - inputFields: () => ({ - domainId: { - type: GraphQLNonNull(GraphQLID), - description: 'The global id of the domain that is being updated.', - }, - orgId: { - type: GraphQLNonNull(GraphQLID), - description: - 'The global ID of the organization used for permission checks.', - }, - domain: { - type: Domain, - description: 'The new url of the of the old domain.', - }, - selectors: { - type: new GraphQLList(Selectors), - description: - 'The updated DKIM selector strings corresponding to this domain.', - }, - }), - outputFields: () => ({ - result: { - type: updateDomainUnion, - description: - '`UpdateDomainUnion` returning either a `Domain`, or `DomainError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - validators: { cleanseInput }, - loaders: { loadDomainByKey, loadOrgByKey }, - }, - ) => { - // Get User - const user = await userRequired() - - verifiedRequired({ user }) - - const { id: domainId } = fromGlobalId(cleanseInput(args.domainId)) - const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - const updatedDomain = cleanseInput(args.domain) - - let selectors - if (typeof args.selectors !== 'undefined') { - selectors = args.selectors.map((selector) => cleanseInput(selector)) - } else { - selectors = null - } - - // Check to see if domain exists - const domain = await loadDomainByKey.load(domainId) - - if (typeof domain === 'undefined') { - console.warn( - `User: ${userKey} attempted to update domain: ${domainId}, however there is no domain associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to update unknown domain.`), - } - } - - // Check to see if org exists - const org = await loadOrgByKey.load(orgId) - - if (typeof org === 'undefined') { - console.warn( - `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however there is no org associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to update domain in an unknown org.`), - } - } - - // Check permission - const permission = await checkPermission({ orgId: org._id }) - - if ( - permission !== 'user' && - permission !== 'admin' && - permission !== 'super_admin' - ) { - console.warn( - `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however they do not have permission in that org.`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact organization user for help with updating this domain.`, - ), - } - } - - // Check to see if org has a claim to this domain - let countCursor - try { - countCursor = await query` - WITH claims, domains, organizations - FOR v, e IN 1..1 ANY ${domain._id} claims - FILTER e._from == ${org._id} - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} attempted to update domain: ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to update domain. Please try again.`)) - } - - if (countCursor.count < 1) { - console.warn( - `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however that org has no claims to that domain.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to update domain that does not belong to the given organization.`, - ), - } - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Update domain - const domainToInsert = { - domain: updatedDomain.toLowerCase() || domain.domain.toLowerCase(), - lastRan: domain.lastRan, - selectors: selectors || domain.selectors, - } - - try { - await trx.step( - async () => - await query` - WITH domains - UPSERT { _key: ${domain._key} } - INSERT ${domainToInsert} - UPDATE ${domainToInsert} - IN domains - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred when user: ${userKey} attempted to update domain: ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to update domain. Please try again.`)) - } - - // Commit transaction - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction commit error occurred when user: ${userKey} attempted to update domain: ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to update domain. Please try again.`)) - } - - // Clear dataloader and load updated domain - await loadDomainByKey.clear(domain._key) - const returnDomain = await loadDomainByKey.load(domain._key) - - console.info(`User: ${userKey} successfully updated domain: ${domainId}.`) - returnDomain.id = returnDomain._key - - return returnDomain - }, -}) diff --git a/api-js/src/domain/objects/__tests__/domain-result.test.js b/api-js/src/domain/objects/__tests__/domain-result.test.js deleted file mode 100644 index 527a06955b..0000000000 --- a/api-js/src/domain/objects/__tests__/domain-result.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { GraphQLString } from 'graphql' - -import { domainResultType } from '../domain-result' -import { domainType } from '../domain' - -describe('given the domainResultType object', () => { - describe('testing the field definitions', () => { - it('has an status field', () => { - const demoType = domainResultType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(GraphQLString) - }) - it('has a domain type', () => { - const demoType = domainResultType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - }) - - describe('testing the field resolvers', () => { - describe('testing the status resolver', () => { - it('returns the resolved field', () => { - const demoType = domainResultType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the domain resolver', () => { - const demoType = domainResultType.getFields() - - expect( - demoType.domain.resolve({ domain: { id: 1, domain: 'test.gc.ca' } }), - ).toEqual({ id: 1, domain: 'test.gc.ca' }) - }) - }) -}) diff --git a/api-js/src/domain/objects/__tests__/domain-status.test.js b/api-js/src/domain/objects/__tests__/domain-status.test.js deleted file mode 100644 index a43babd746..0000000000 --- a/api-js/src/domain/objects/__tests__/domain-status.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import { StatusEnum } from '../../../enums' -import { domainStatus } from '../domain-status' - -describe('given the domainStatus object', () => { - describe('testing its field definitions', () => { - it('has a dkim field', () => { - const demoType = domainStatus.getFields() - - expect(demoType).toHaveProperty('dkim') - expect(demoType.dkim.type).toMatchObject(StatusEnum) - }) - it('has a dmarc field', () => { - const demoType = domainStatus.getFields() - - expect(demoType).toHaveProperty('dmarc') - expect(demoType.dmarc.type).toMatchObject(StatusEnum) - }) - it('has a https field', () => { - const demoType = domainStatus.getFields() - - expect(demoType).toHaveProperty('https') - expect(demoType.https.type).toMatchObject(StatusEnum) - }) - it('has a spf field', () => { - const demoType = domainStatus.getFields() - - expect(demoType).toHaveProperty('spf') - expect(demoType.spf.type).toMatchObject(StatusEnum) - }) - it('has a ssl field', () => { - const demoType = domainStatus.getFields() - - expect(demoType).toHaveProperty('ssl') - expect(demoType.ssl.type).toMatchObject(StatusEnum) - }) - }) - - describe('testing its field resolvers', () => { - describe('testing the dkim resolver', () => { - it('returns the resolved value', () => { - const demoType = domainStatus.getFields() - - expect(demoType.dkim.resolve({ dkim: 'pass' })).toEqual('pass') - }) - }) - describe('testing the dmarc resolver', () => { - it('returns the resolved value', () => { - const demoType = domainStatus.getFields() - - expect(demoType.dmarc.resolve({ dmarc: 'pass' })).toEqual('pass') - }) - }) - describe('testing the https resolver', () => { - it('returns the resolved value', () => { - const demoType = domainStatus.getFields() - - expect(demoType.https.resolve({ https: 'pass' })).toEqual('pass') - }) - }) - describe('testing the spf resolver', () => { - it('returns the resolved value', () => { - const demoType = domainStatus.getFields() - - expect(demoType.spf.resolve({ spf: 'pass' })).toEqual('pass') - }) - }) - describe('testing the ssl resolver', () => { - it('returns the resolved value', () => { - const demoType = domainStatus.getFields() - - expect(demoType.ssl.resolve({ ssl: 'pass' })).toEqual('pass') - }) - }) - }) -}) diff --git a/api-js/src/domain/objects/__tests__/domain.test.js b/api-js/src/domain/objects/__tests__/domain.test.js deleted file mode 100644 index 2c91935f11..0000000000 --- a/api-js/src/domain/objects/__tests__/domain.test.js +++ /dev/null @@ -1,619 +0,0 @@ -import { - GraphQLNonNull, - GraphQLID, - GraphQLList, - GraphQLString, - GraphQLBoolean, -} from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import { tokenize } from '../../../auth' -import { organizationConnection } from '../../../organization/objects' -import { domainStatus } from '../domain-status' -import { dmarcSummaryType } from '../../../dmarc-summaries/objects' -import { emailScanType } from '../../../email-scan/objects' -import { webScanType } from '../../../web-scan/objects' -import { domainType } from '../../index' -import { Domain, Selectors } from '../../../scalars' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' - -describe('given the domain object', () => { - describe('testing its field definitions', () => { - it('has an id field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a domain field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(Domain) - }) - it('has a dmarcPhase field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('dmarcPhase') - expect(demoType.dmarcPhase.type).toMatchObject(GraphQLString) - }) - it('has a hasDMARCReport field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('hasDMARCReport') - expect(demoType.hasDMARCReport.type).toMatchObject(GraphQLBoolean) - }) - it('has a lastRan field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('lastRan') - expect(demoType.lastRan.type).toMatchObject(GraphQLString) - }) - it('has a selectors field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('selectors') - expect(demoType.selectors.type).toMatchObject(GraphQLList(Selectors)) - }) - it('has a status field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(domainStatus) - }) - it('has an organizations field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('organizations') - expect(demoType.organizations.type).toMatchObject( - organizationConnection.connectionType, - ) - }) - it('has an email field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('email') - expect(demoType.email.type).toMatchObject(emailScanType) - }) - it('has a web field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('web') - expect(demoType.web.type).toMatchObject(webScanType) - }) - it('has a dmarcSummaryByPeriod field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('dmarcSummaryByPeriod') - expect(demoType.dmarcSummaryByPeriod.type).toMatchObject(dmarcSummaryType) - }) - it('has a yearlyDmarcSummaries field', () => { - const demoType = domainType.getFields() - - expect(demoType).toHaveProperty('yearlyDmarcSummaries') - expect(demoType.yearlyDmarcSummaries.type).toMatchObject( - GraphQLList(dmarcSummaryType), - ) - }) - }) - describe('testing the field resolvers', () => { - const consoleOutput = [] - const mockedWarn = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('testing the id resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('domain', 1), - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - expect(demoType.domain.resolve({ domain: 'test.gc.ca' })).toEqual( - 'test.gc.ca', - ) - }) - }) - describe('testing the dmarcPhase resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - expect( - demoType.dmarcPhase.resolve({ phase: 'not implemented' }), - ).toEqual('not implemented') - }) - }) - describe('testing the hasDMARCReport resolver', () => { - describe('user has access to dmarc reports', () => { - it('returns true', async () => { - const demoType = domainType.getFields() - - const mockedUserRequired = jest.fn().mockReturnValue(true) - const mockedCheckOwnership = jest.fn().mockReturnValue(true) - - await expect( - demoType.hasDMARCReport.resolve( - { _id: 1 }, - {}, - { - auth: { - checkDomainOwnership: mockedCheckOwnership, - userRequired: mockedUserRequired, - }, - }, - ), - ).resolves.toEqual(true) - }) - }) - describe('user does not have access to dmarc reports', () => { - it('returns false', async () => { - const demoType = domainType.getFields() - - const mockedUserRequired = jest.fn().mockReturnValue(true) - const mockedCheckOwnership = jest.fn().mockReturnValue(false) - - await expect( - demoType.hasDMARCReport.resolve( - { _id: 1 }, - {}, - { - auth: { - checkDomainOwnership: mockedCheckOwnership, - userRequired: mockedUserRequired, - }, - }, - ), - ).resolves.toEqual(false) - }) - }) - }) - describe('testing the lastRan resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - expect( - demoType.lastRan.resolve({ lastRan: '2020-10-02T12:43:39Z' }), - ).toEqual('2020-10-02T12:43:39Z') - }) - }) - describe('testing the selectors resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - const selectors = ['selector1._domainkey', 'selector2._domainkey'] - - expect(demoType.selectors.resolve({ selectors })).toEqual([ - 'selector1._domainkey', - 'selector2._domainkey', - ]) - }) - }) - describe('testing the status resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - const status = { - dkim: 'pass', - dmarc: 'pass', - https: 'info', - spf: 'fail', - ssl: 'fail', - } - - expect(demoType.status.resolve({ status })).toEqual({ - dkim: 'pass', - dmarc: 'pass', - https: 'info', - spf: 'fail', - ssl: 'fail', - }) - }) - }) - describe('testing the organizations resolver', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('organizations', '1'), - node: { - _id: 'organizations/`', - _key: '1', - _rev: 'rev', - _type: 'organization', - id: '1', - verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, - domainCount: 2, - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('organizations', '1'), - endCursor: toGlobalId('organizations', '1'), - }, - } - - expect( - demoType.organizations.resolve( - { _id: '1' }, - { first: 1 }, - { - loaders: { - loadOrgConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), - }, - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(false), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the email resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - expect(demoType.email.resolve({ _id: '1', _key: '1' })).toEqual({ - _id: '1', - _key: '1', - }) - }) - }) - describe('testing the web resolver', () => { - it('returns the resolved value', () => { - const demoType = domainType.getFields() - - expect(demoType.web.resolve({ _id: '1', _key: '1' })).toEqual({ - _id: '1', - _key: '1', - }) - }) - }) - describe('testing the dmarcSummaryByPeriod resolver', () => { - let i18n - describe('user has domain ownership permission', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - - const data = { - _id: 'domains/1', - _key: '1', - domain: 'test1.gc.ca', - } - - await expect( - demoType.dmarcSummaryByPeriod.resolve( - data, - { - month: 'january', - year: '2021', - }, - { - userKey: '1', - loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod: jest - .fn() - .mockReturnValue({ - _to: 'dmarcSummaries/1', - }), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), - }, - auth: { - checkDomainOwnership: jest.fn().mockReturnValue(true), - userRequired: jest.fn(), - tokenize, - }, - }, - ), - ).resolves.toEqual({ - _id: 'dmarcSummaries/1', - domainKey: '1', - startDate: '2021-01-01', - }) - }) - }) - describe('users language is english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user does not have domain ownership permission', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - - const data = { - _id: 'domains/1', - _key: '1', - domain: 'test1.gc.ca', - } - - await expect( - demoType.dmarcSummaryByPeriod.resolve( - data, - {}, - { - i18n, - userKey: '1', - loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), - }, - auth: { - checkDomainOwnership: jest.fn().mockReturnValue(false), - userRequired: jest.fn(), - }, - }, - ), - ).rejects.toEqual( - new Error( - 'Unable to retrieve DMARC report information for: test1.gc.ca', - ), - ) - - expect(consoleOutput).toEqual([ - `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, - ]) - }) - }) - }) - describe('users language is french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user does not have domain ownership permission', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - - const data = { - _id: 'domains/1', - _key: '1', - domain: 'test1.gc.ca', - } - - await expect( - demoType.dmarcSummaryByPeriod.resolve( - data, - {}, - { - i18n, - userKey: '1', - loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), - }, - auth: { - checkDomainOwnership: jest.fn().mockReturnValue(false), - userRequired: jest.fn(), - }, - }, - ), - ).rejects.toEqual( - new Error( - 'Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca', - ), - ) - - expect(consoleOutput).toEqual([ - `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, - ]) - }) - }) - }) - }) - describe('testing the yearlyDmarcSummaries resolver', () => { - let i18n - describe('user has domain ownership permission', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - const data = { - _id: 'domains/1', - _key: '1', - domain: 'test1.gc.ca', - } - - await expect( - demoType.yearlyDmarcSummaries.resolve( - data, - {}, - { - userKey: '1', - loaders: { - loadDmarcYearlySumEdge: jest.fn().mockReturnValue([ - { - domainKey: '1', - _to: 'dmarcSummaries/1', - startDate: '2021-01-01', - }, - ]), - }, - auth: { - checkDomainOwnership: jest.fn().mockReturnValue(true), - userRequired: jest.fn(), - }, - }, - ), - ).resolves.toEqual([ - { - _id: 'dmarcSummaries/1', - domainKey: '1', - startDate: '2021-01-01', - }, - ]) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user does not have domain ownership permission', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - - const data = { - _id: 'domains/1', - _key: '1', - domain: 'test1.gc.ca', - } - - await expect( - demoType.yearlyDmarcSummaries.resolve( - data, - {}, - { - i18n, - request: { - language: 'fr', - }, - userKey: '1', - loaders: { - loadDmarcYearlySumEdge: jest.fn(), - }, - auth: { - checkDomainOwnership: jest.fn().mockReturnValue(false), - userRequired: jest.fn(), - }, - }, - ), - ).rejects.toEqual( - new Error( - 'Unable to retrieve DMARC report information for: test1.gc.ca', - ), - ) - expect(consoleOutput).toEqual([ - `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user does not have domain ownership permission', () => { - it('returns the resolved value', async () => { - const demoType = domainType.getFields() - - const data = { - _id: 'domains/1', - _key: '1', - domain: 'test1.gc.ca', - } - - await expect( - demoType.yearlyDmarcSummaries.resolve( - data, - {}, - { - i18n, - userKey: '1', - loaders: { - loadDmarcYearlySumEdge: jest.fn(), - }, - auth: { - checkDomainOwnership: jest.fn().mockReturnValue(false), - userRequired: jest.fn(), - }, - }, - ), - ).rejects.toEqual( - new Error( - 'Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca', - ), - ) - expect(consoleOutput).toEqual([ - `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/domain/objects/domain-connection.js b/api-js/src/domain/objects/domain-connection.js deleted file mode 100644 index aa6790856a..0000000000 --- a/api-js/src/domain/objects/domain-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { domainType } from './domain' - -export const domainConnection = connectionDefinitions({ - name: 'Domain', - nodeType: domainType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of domains the user has access to.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/domain/objects/domain-result.js b/api-js/src/domain/objects/domain-result.js deleted file mode 100644 index 72eebb2e09..0000000000 --- a/api-js/src/domain/objects/domain-result.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' - -import { domainType } from './domain' - -export const domainResultType = new GraphQLObjectType({ - name: 'DomainResult', - description: - 'This object is used to inform the user that no errors were encountered while removing a domain.', - fields: () => ({ - status: { - type: GraphQLString, - description: 'Informs the user if the domain removal was successful.', - resolve: ({ status }) => status, - }, - domain: { - type: domainType, - description: 'The domain that is being mutated.', - resolve: ({ domain }) => domain, - }, - }), -}) diff --git a/api-js/src/domain/objects/domain-status.js b/api-js/src/domain/objects/domain-status.js deleted file mode 100644 index 732bf3a2f3..0000000000 --- a/api-js/src/domain/objects/domain-status.js +++ /dev/null @@ -1,35 +0,0 @@ -import { GraphQLObjectType } from 'graphql' -import { StatusEnum } from '../../enums' - -export const domainStatus = new GraphQLObjectType({ - name: 'DomainStatus', - description: - 'This object contains how the domain is doing on the various scans we preform, based on the latest scan data.', - fields: () => ({ - dkim: { - type: StatusEnum, - description: 'DKIM Status', - resolve: ({ dkim }) => dkim, - }, - dmarc: { - type: StatusEnum, - description: 'DMARC Status', - resolve: ({ dmarc }) => dmarc, - }, - https: { - type: StatusEnum, - description: 'HTTPS Status', - resolve: ({ https }) => https, - }, - spf: { - type: StatusEnum, - description: 'SPF Status', - resolve: ({ spf }) => spf, - }, - ssl: { - type: StatusEnum, - description: 'SSL Status', - resolve: ({ ssl }) => ssl, - }, - }), -}) diff --git a/api-js/src/domain/objects/domain.js b/api-js/src/domain/objects/domain.js deleted file mode 100644 index b7b49051c4..0000000000 --- a/api-js/src/domain/objects/domain.js +++ /dev/null @@ -1,224 +0,0 @@ -import { t } from '@lingui/macro' -import { - GraphQLBoolean, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString, -} from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' - -import { domainStatus } from './domain-status' -import { PeriodEnums } from '../../enums' -import { nodeInterface } from '../../node' -import { Domain, Selectors, Year } from '../../scalars' -import { dmarcSummaryType } from '../../dmarc-summaries/objects' -import { emailScanType } from '../../email-scan/objects' -import { webScanType } from '../../web-scan/objects' -import { organizationOrder } from '../../organization/inputs' -import { organizationConnection } from '../../organization/objects' - -export const domainType = new GraphQLObjectType({ - name: 'Domain', - fields: () => ({ - id: globalIdField('domain'), - domain: { - type: Domain, - description: 'Domain that scans will be ran on.', - resolve: ({ domain }) => domain, - }, - dmarcPhase: { - type: GraphQLString, - description: 'The current dmarc phase the domain is compliant to.', - resolve: ({ phase }) => phase, - }, - hasDMARCReport: { - type: GraphQLBoolean, - description: 'Whether or not the domain has a aggregate dmarc report.', - resolve: async ( - { _id }, - _, - { auth: { checkDomainOwnership, userRequired } }, - ) => { - await userRequired() - const hasDMARCReport = await checkDomainOwnership({ - domainId: _id, - }) - - return hasDMARCReport - }, - }, - lastRan: { - type: GraphQLString, - description: 'The last time that a scan was ran on this domain.', - resolve: ({ lastRan }) => lastRan, - }, - selectors: { - type: new GraphQLList(Selectors), - description: - 'Domain Keys Identified Mail (DKIM) selector strings associated with domain.', - resolve: ({ selectors }) => selectors, - }, - status: { - type: domainStatus, - description: 'The domains scan status, based on the latest scan data.', - resolve: ({ status }) => status, - }, - organizations: { - type: organizationConnection.connectionType, - args: { - orderBy: { - type: organizationOrder, - description: 'Ordering options for organization connections', - }, - search: { - type: GraphQLString, - description: 'String argument used to search for organizations.', - }, - isAdmin: { - type: GraphQLBoolean, - description: - 'Filter orgs based off of the user being an admin of them.', - }, - includeSuperAdminOrg: { - type: GraphQLBoolean, - description: - 'Filter org list to either include or exclude the super admin org.', - }, - ...connectionArgs, - }, - description: 'The organization that this domain belongs to.', - resolve: async ( - { _id }, - args, - { - auth: { checkSuperAdmin }, - loaders: { loadOrgConnectionsByDomainId }, - }, - ) => { - const isSuperAdmin = await checkSuperAdmin() - - const orgs = await loadOrgConnectionsByDomainId({ - domainId: _id, - isSuperAdmin, - ...args, - }) - return orgs - }, - }, - email: { - type: emailScanType, - description: 'DKIM, DMARC, and SPF scan results.', - resolve: ({ _id, _key }) => { - return { _id, _key } - }, - }, - web: { - type: webScanType, - description: 'HTTPS, and SSL scan results.', - resolve: ({ _id, _key }) => { - return { _id, _key } - }, - }, - dmarcSummaryByPeriod: { - description: 'Summarized DMARC aggregate reports.', - args: { - month: { - type: GraphQLNonNull(PeriodEnums), - description: 'The month in which the returned data is relevant to.', - }, - year: { - type: GraphQLNonNull(Year), - description: 'The year in which the returned data is relevant to.', - }, - }, - type: dmarcSummaryType, - resolve: async ( - { _id, _key, domain }, - { month, year }, - { - i18n, - userKey, - loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod, - loadStartDateFromPeriod, - }, - auth: { checkDomainOwnership, userRequired }, - }, - ) => { - await userRequired() - const permitted = await checkDomainOwnership({ - domainId: _id, - }) - - if (!permitted) { - console.warn( - `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, - ) - throw new Error( - i18n._( - t`Unable to retrieve DMARC report information for: ${domain}`, - ), - ) - } - - const startDate = loadStartDateFromPeriod({ period: month, year }) - - const dmarcSummaryEdge = await loadDmarcSummaryEdgeByDomainIdAndPeriod({ - domainId: _id, - startDate, - }) - - return { - domainKey: _key, - _id: dmarcSummaryEdge._to, - startDate: startDate, - } - }, - }, - yearlyDmarcSummaries: { - description: 'Yearly summarized DMARC aggregate reports.', - type: new GraphQLList(dmarcSummaryType), - resolve: async ( - { _id, _key, domain }, - __, - { - i18n, - userKey, - loaders: { loadDmarcYearlySumEdge }, - auth: { checkDomainOwnership, userRequired }, - }, - ) => { - await userRequired() - const permitted = await checkDomainOwnership({ - domainId: _id, - }) - - if (!permitted) { - console.warn( - `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, - ) - throw new Error( - i18n._( - t`Unable to retrieve DMARC report information for: ${domain}`, - ), - ) - } - - const dmarcSummaryEdges = await loadDmarcYearlySumEdge({ - domainId: _id, - }) - - const edges = dmarcSummaryEdges.map((edge) => ({ - domainKey: _key, - _id: edge._to, - startDate: edge.startDate, - })) - - return edges - }, - }, - }), - interfaces: [nodeInterface], - description: 'Domain object containing information for a given domain.', -}) diff --git a/api-js/src/domain/objects/index.js b/api-js/src/domain/objects/index.js deleted file mode 100644 index fab5076f95..0000000000 --- a/api-js/src/domain/objects/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export * from './domain' -export * from './domain-connection' -export * from './domain-error' -export * from './domain-result' -export * from './domain-status' diff --git a/api-js/src/domain/queries/find-domain-by-domain.js b/api-js/src/domain/queries/find-domain-by-domain.js deleted file mode 100644 index e558e9b948..0000000000 --- a/api-js/src/domain/queries/find-domain-by-domain.js +++ /dev/null @@ -1,59 +0,0 @@ -import { GraphQLNonNull } from 'graphql' -import { t } from '@lingui/macro' -import { Domain } from '../../scalars' - -import { domainType } from '../objects' - -export const findDomainByDomain = { - type: domainType, - description: 'Retrieve a specific domain by providing a domain.', - args: { - domain: { - type: GraphQLNonNull(Domain), - description: 'The domain you wish to retrieve information for.', - }, - }, - resolve: async ( - _, - args, - { - i18n, - auth: { checkDomainPermission, userRequired, verifiedRequired }, - loaders: { loadDomainByDomain }, - validators: { cleanseInput }, - }, - ) => { - // Get User - const user = await userRequired() - verifiedRequired({ user }) - - // Cleanse input - const domainInput = cleanseInput(args.domain) - - // Retrieve domain by domain - const domain = await loadDomainByDomain.load(domainInput) - - if (typeof domain === 'undefined') { - console.warn(`User ${user._key} could not retrieve domain.`) - throw new Error(i18n._(t`Unable to find the requested domain.`)) - } - - // Check user permission for domain access - const permitted = await checkDomainPermission({ domainId: domain._id }) - - if (!permitted) { - console.warn(`User ${user._key} could not retrieve domain.`) - throw new Error( - i18n._( - t`Permission Denied: Please contact organization user for help with retrieving this domain.`, - ), - ) - } - - console.info( - `User ${user._key} successfully retrieved domain ${domain._key}.`, - ) - - return domain - }, -} diff --git a/api-js/src/domain/queries/find-my-domains.js b/api-js/src/domain/queries/find-my-domains.js deleted file mode 100644 index 8e4afe561c..0000000000 --- a/api-js/src/domain/queries/find-my-domains.js +++ /dev/null @@ -1,49 +0,0 @@ -import { GraphQLBoolean, GraphQLString } from 'graphql' -import { connectionArgs } from 'graphql-relay' - -import { domainOrder } from '../inputs' -import { domainConnection } from '../objects' - -export const findMyDomains = { - type: domainConnection.connectionType, - description: 'Select domains a user has access to.', - args: { - orderBy: { - type: domainOrder, - description: 'Ordering options for domain connections.', - }, - ownership: { - type: GraphQLBoolean, - description: - 'Limit domains to those that belong to an organization that has ownership.', - }, - search: { - type: GraphQLString, - description: 'String used to search for domains.', - }, - ...connectionArgs, - }, - resolve: async ( - _, - args, - { - userKey, - auth: { checkSuperAdmin, userRequired, verifiedRequired }, - loaders: { loadDomainConnectionsByUserId }, - }, - ) => { - const user = await userRequired() - verifiedRequired({ user }) - - const isSuperAdmin = await checkSuperAdmin() - - const domainConnections = await loadDomainConnectionsByUserId({ - isSuperAdmin, - ...args, - }) - - console.info(`User: ${userKey} successfully retrieved their domains.`) - - return domainConnections - }, -} diff --git a/api-js/src/domain/unions/index.js b/api-js/src/domain/unions/index.js deleted file mode 100644 index 79289f6b1d..0000000000 --- a/api-js/src/domain/unions/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './create-domain-union' -export * from './remove-domain-union' -export * from './update-domain-union' diff --git a/api-js/src/email-scan/index.js b/api-js/src/email-scan/index.js deleted file mode 100644 index 20b59a8da4..0000000000 --- a/api-js/src/email-scan/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './loaders' -export * from './objects' -export * from './subscriptions' diff --git a/api-js/src/email-scan/inputs/__tests__/dkim-order.test.js b/api-js/src/email-scan/inputs/__tests__/dkim-order.test.js deleted file mode 100644 index e14e8e01b8..0000000000 --- a/api-js/src/email-scan/inputs/__tests__/dkim-order.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { dkimOrder } from '../dkim-order' -import { OrderDirection, DkimOrderField } from '../../../enums' - -describe('given the dkimOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = dkimOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = dkimOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject(GraphQLNonNull(DkimOrderField)) - }) - }) -}) diff --git a/api-js/src/email-scan/inputs/__tests__/dkim-result-order.test.js b/api-js/src/email-scan/inputs/__tests__/dkim-result-order.test.js deleted file mode 100644 index 283c77c247..0000000000 --- a/api-js/src/email-scan/inputs/__tests__/dkim-result-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { dkimResultOrder } from '../dkim-result-order' -import { OrderDirection, DkimResultOrderField } from '../../../enums' - -describe('given the dkimResultOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = dkimResultOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = dkimResultOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(DkimResultOrderField), - ) - }) - }) -}) diff --git a/api-js/src/email-scan/inputs/__tests__/dmarc-order.test.js b/api-js/src/email-scan/inputs/__tests__/dmarc-order.test.js deleted file mode 100644 index e086a4b897..0000000000 --- a/api-js/src/email-scan/inputs/__tests__/dmarc-order.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { dmarcOrder } from '../dmarc-order' -import { OrderDirection, DmarcOrderField } from '../../../enums' - -describe('given the dmarcOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = dmarcOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = dmarcOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject(GraphQLNonNull(DmarcOrderField)) - }) - }) -}) diff --git a/api-js/src/email-scan/inputs/__tests__/spf-order.test.js b/api-js/src/email-scan/inputs/__tests__/spf-order.test.js deleted file mode 100644 index 2b53822ef1..0000000000 --- a/api-js/src/email-scan/inputs/__tests__/spf-order.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { spfOrder } from '../spf-order' -import { OrderDirection, SpfOrderField } from '../../../enums' - -describe('given the spfOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = spfOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = spfOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject(GraphQLNonNull(SpfOrderField)) - }) - }) -}) diff --git a/api-js/src/email-scan/inputs/dkim-order.js b/api-js/src/email-scan/inputs/dkim-order.js deleted file mode 100644 index f2d0438bd1..0000000000 --- a/api-js/src/email-scan/inputs/dkim-order.js +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' - -import { OrderDirection, DkimOrderField } from '../../enums' - -export const dkimOrder = new GraphQLInputObjectType({ - name: 'DKIMOrder', - description: 'Ordering options for DKIM connections.', - fields: () => ({ - field: { - type: GraphQLNonNull(DkimOrderField), - description: 'The field to order DKIM scans by.', - }, - direction: { - type: GraphQLNonNull(OrderDirection), - description: 'The ordering direction.', - }, - }), -}) diff --git a/api-js/src/email-scan/inputs/dkim-result-order.js b/api-js/src/email-scan/inputs/dkim-result-order.js deleted file mode 100644 index 1ec8f62c75..0000000000 --- a/api-js/src/email-scan/inputs/dkim-result-order.js +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' - -import { OrderDirection, DkimResultOrderField } from '../../enums' - -export const dkimResultOrder = new GraphQLInputObjectType({ - name: 'DKIMResultOrder', - description: 'Ordering options for DKIM Result connections.', - fields: () => ({ - field: { - type: GraphQLNonNull(DkimResultOrderField), - description: 'The field to order DKIM Results by.', - }, - direction: { - type: GraphQLNonNull(OrderDirection), - description: 'The ordering direction.', - }, - }), -}) diff --git a/api-js/src/email-scan/inputs/dmarc-order.js b/api-js/src/email-scan/inputs/dmarc-order.js deleted file mode 100644 index a7ed915d27..0000000000 --- a/api-js/src/email-scan/inputs/dmarc-order.js +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' - -import { OrderDirection, DmarcOrderField } from '../../enums' - -export const dmarcOrder = new GraphQLInputObjectType({ - name: 'DMARCOrder', - description: 'Ordering options for DMARC connections.', - fields: () => ({ - field: { - type: GraphQLNonNull(DmarcOrderField), - description: 'The field to order DMARC scans by.', - }, - direction: { - type: GraphQLNonNull(OrderDirection), - description: 'The ordering direction.', - }, - }), -}) diff --git a/api-js/src/email-scan/inputs/index.js b/api-js/src/email-scan/inputs/index.js deleted file mode 100644 index a8cb1f599e..0000000000 --- a/api-js/src/email-scan/inputs/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './dkim-order' -export * from './dkim-result-order' -export * from './dmarc-order' -export * from './spf-order' diff --git a/api-js/src/email-scan/inputs/spf-order.js b/api-js/src/email-scan/inputs/spf-order.js deleted file mode 100644 index c0b2b1a12b..0000000000 --- a/api-js/src/email-scan/inputs/spf-order.js +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' - -import { OrderDirection, SpfOrderField } from '../../enums' - -export const spfOrder = new GraphQLInputObjectType({ - name: 'SPFOrder', - description: 'Ordering options for SPF connections.', - fields: () => ({ - field: { - type: GraphQLNonNull(SpfOrderField), - description: 'The field to order SPF scans by.', - }, - direction: { - type: GraphQLNonNull(OrderDirection), - description: 'The ordering direction.', - }, - }), -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-dkim-by-key.test.js b/api-js/src/email-scan/loaders/__tests__/load-dkim-by-key.test.js deleted file mode 100644 index 69458395c4..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-dkim-by-key.test.js +++ /dev/null @@ -1,215 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadDkimByKey } from '../load-dkim-by-key' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadDkimByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(async () => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.dkim.save({}) - await collections.dkim.save({}) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single dkim scan', async () => { - // Get dkim from db - const expectedCursor = await query` - FOR dkimScan IN dkim - SORT dkimScan._key ASC LIMIT 1 - RETURN MERGE({ id: dkimScan._key, _type: "dkim" }, dkimScan) - ` - const expectedDkim = await expectedCursor.next() - - const loader = loadDkimByKey({ query, i18n }) - const dkim = await loader.load(expectedDkim._key) - - expect(dkim).toEqual(expectedDkim) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim scans', async () => { - const dkimKeys = [] - const expectedDkimScans = [] - const expectedCursor = await query` - FOR dkimScan IN dkim - RETURN MERGE({ id: dkimScan._key, _type: "dkim" }, dkimScan) - ` - - while (expectedCursor.hasMore) { - const tempDkim = await expectedCursor.next() - dkimKeys.push(tempDkim._key) - expectedDkimScans.push(tempDkim) - } - - const loader = loadDkimByKey({ query, i18n }) - const dkimScans = await loader.loadMany(dkimKeys) - expect(dkimScans).toEqual(expectedDkimScans) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadDkimByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find DKIM scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDkimByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadDkimByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find DKIM scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDkimByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadDkimByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDkimByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadDkimByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDkimByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-dkim-connections-by-domain-id.test.js b/api-js/src/email-scan/loaders/__tests__/load-dkim-connections-by-domain-id.test.js deleted file mode 100644 index 3b2bac9dcc..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-dkim-connections-by-domain-id.test.js +++ /dev/null @@ -1,1330 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { loadDkimConnectionsByDomainId, loadDkimByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load dkim connection function', () => { - let query, - drop, - truncate, - collections, - user, - domain, - dkimScan1, - dkimScan2, - i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - dkimScan1 = await collections.dkim.save({ - timestamp: '2020-10-02T12:43:39Z', - }) - dkimScan2 = await collections.dkim.save({ - timestamp: '2020-10-03T12:43:39Z', - }) - await collections.domainsDKIM.save({ - _to: dkimScan1._id, - _from: domain._id, - }) - await collections.domainsDKIM.save({ - _to: dkimScan2._id, - _from: domain._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('using after cursor', () => { - it('returns dkim scan(s) after a given node id', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([ - dkimScan1._key, - dkimScan2._key, - ]) - - expectedDkimScans[0].id = expectedDkimScans[0]._key - expectedDkimScans[0].domainId = domain._id - - expectedDkimScans[1].id = expectedDkimScans[1]._key - expectedDkimScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - after: toGlobalId('dkim', expectedDkimScans[0]._key), - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[1]._key), - node: { - ...expectedDkimScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dkim', expectedDkimScans[1]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[1]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dkim scan(s) before a given node id', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([ - dkimScan1._key, - dkimScan2._key, - ]) - - expectedDkimScans[0].id = expectedDkimScans[0]._key - expectedDkimScans[0].domainId = domain._id - - expectedDkimScans[1].id = expectedDkimScans[1]._key - expectedDkimScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - before: toGlobalId('dkim', expectedDkimScans[1]._key), - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[0]._key), - node: { - ...expectedDkimScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([ - dkimScan1._key, - dkimScan2._key, - ]) - - expectedDkimScans[0].id = expectedDkimScans[0]._key - expectedDkimScans[0].domainId = domain._id - - expectedDkimScans[1].id = expectedDkimScans[1]._key - expectedDkimScans[1].domainId = domain._id - - const connectionArgs = { - first: 1, - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[0]._key), - node: { - ...expectedDkimScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([ - dkimScan1._key, - dkimScan2._key, - ]) - - expectedDkimScans[1].id = expectedDkimScans[1]._key - expectedDkimScans[1].domainId = domain._id - - const connectionArgs = { - last: 1, - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[1]._key), - node: { - ...expectedDkimScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dkim', expectedDkimScans[1]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[1]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('use date filters', () => { - let dkimScan3 - beforeEach(async () => { - dkimScan3 = await collections.dkim.save({ - timestamp: '2020-10-04T12:43:39Z', - }) - await collections.domainsDKIM.save({ - _to: dkimScan3._id, - _from: domain._id, - }) - }) - describe('using start date filter', () => { - it('returns dkim scans at and after the start date', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([ - dkimScan2._key, - dkimScan3._key, - ]) - - expectedDkimScans[0].id = expectedDkimScans[0]._key - expectedDkimScans[0].domainId = domain._id - - expectedDkimScans[1].id = expectedDkimScans[1]._key - expectedDkimScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03', - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[0]._key), - node: { - ...expectedDkimScans[0], - }, - }, - { - cursor: toGlobalId('dkim', expectedDkimScans[1]._key), - node: { - ...expectedDkimScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[1]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('using end date filter', () => { - it('returns dkim scans at and before the end date', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([ - dkimScan1._key, - dkimScan2._key, - ]) - - expectedDkimScans[0].id = expectedDkimScans[0]._key - expectedDkimScans[0].domainId = domain._id - - expectedDkimScans[1].id = expectedDkimScans[1]._key - expectedDkimScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - endDate: '2020-10-03T13:50:00Z', - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[0]._key), - node: { - ...expectedDkimScans[0], - }, - }, - { - cursor: toGlobalId('dkim', expectedDkimScans[1]._key), - node: { - ...expectedDkimScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[1]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('using start and end date filters', () => { - it('returns dkim scan on a specific date', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDkimByKey({ query }) - const expectedDkimScans = await dkimLoader.loadMany([dkimScan2._key]) - - expectedDkimScans[0].id = expectedDkimScans[0]._key - expectedDkimScans[0].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03T00:00:00Z', - endDate: '2020-10-03T23:59:59Z', - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScans[0]._key), - node: { - ...expectedDkimScans[0], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - endCursor: toGlobalId('dkim', expectedDkimScans[0]._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - }) - describe('using orderBy field', () => { - let dkimScanOne, dkimScanTwo, dkimScanThree - beforeEach(async () => { - await truncate() - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - dkimScanOne = await collections.dkim.save({ - timestamp: '2021-01-26 23:24:34.506578Z', - }) - dkimScanTwo = await collections.dkim.save({ - timestamp: '2021-01-27 23:24:34.506578Z', - }) - dkimScanThree = await collections.dkim.save({ - timestamp: '2021-01-28 23:24:34.506578Z', - }) - await collections.domainsDKIM.save({ - _to: dkimScanOne._id, - _from: domain._id, - }) - await collections.domainsDKIM.save({ - _to: dkimScanTwo._id, - _from: domain._id, - }) - await collections.domainsDKIM.save({ - _to: dkimScanThree._id, - _from: domain._id, - }) - }) - describe('ordering on TIMESTAMP', () => { - describe('direction set to ASC', () => { - it('returns dkim scan', async () => { - const loader = loadDkimByKey({ query, userKey: user._key, i18n }) - const expectedDkimScan = await loader.load(dkimScanTwo._key) - - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dkimScanOne._key), - before: toGlobalId('dkim', dkimScanThree._key), - orderBy: { - field: 'timestamp', - direction: 'ASC', - }, - } - - const dkimScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScan._key), - node: { - domainId: domain._id, - ...expectedDkimScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkim', expectedDkimScan._key), - endCursor: toGlobalId('dkim', expectedDkimScan._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - describe('direction set to DESC', () => { - it('returns dkim scan', async () => { - const loader = loadDkimByKey({ query, userKey: user._key, i18n }) - const expectedDkimScan = await loader.load(dkimScanTwo._key) - - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dkimScanThree._key), - before: toGlobalId('dkim', dkimScanOne._key), - orderBy: { - field: 'timestamp', - direction: 'DESC', - }, - } - - const dkimScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkim', expectedDkimScan._key), - node: { - domainId: domain._id, - ...expectedDkimScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkim', expectedDkimScan._key), - endCursor: toGlobalId('dkim', expectedDkimScan._key), - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dkim scans are found', () => { - it('returns an empty structure', async () => { - await truncate() - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - const dkimScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dkimScans).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('limits are not set', () => { - it('throws an error', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `DKIM` connection.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `DKIM` connection is not supported.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are set below minimum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `DKIM` connection cannot be less than zero.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `DKIM` connection cannot be less than zero.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are set above maximum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 500 records on the `DKIM` connection exceeds the `first` limit of 100 records.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 500 for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 500 records on the `DKIM` connection exceeds the `last` limit of 100 records.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load DKIM scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get dkim information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load DKIM scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get dkim information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('limits are not set', () => { - it('throws an error', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.", - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are set below minimum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `DKIM` ne peut être inférieur à zéro.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `DKIM` ne peut être inférieur à zéro.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are set above maximum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 500 enregistrements sur la connexion `DKIM` dépasse la limite `first` de 100 enregistrements.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 500 for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 500 enregistrements sur la connexion `DKIM` dépasse la limite `last` de 100 enregistrements.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDkimConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get dkim information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDkimConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get dkim information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-dkim-result-by-key.test.js b/api-js/src/email-scan/loaders/__tests__/load-dkim-result-by-key.test.js deleted file mode 100644 index 41cf06e8e6..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-dkim-result-by-key.test.js +++ /dev/null @@ -1,216 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadDkimResultByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadDkimResultByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.dkimResults.save({}) - await collections.dkimResults.save({}) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single dkim result', async () => { - // Get dkim result from db - const expectedCursor = await query` - FOR dkimResult IN dkimResults - SORT dkimResult._key ASC LIMIT 1 - RETURN MERGE({ id: dkimResult._key, _type: "dkimResult" }, dkimResult) - ` - const expectedDkimResult = await expectedCursor.next() - - const loader = loadDkimResultByKey({ query, i18n }) - const dkimResult = await loader.load(expectedDkimResult._key) - - expect(dkimResult).toEqual(expectedDkimResult) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim results', async () => { - const dkimResultKeys = [] - const expectedDkimResults = [] - const expectedCursor = await query` - FOR dkimResult IN dkimResults - RETURN MERGE({ id: dkimResult._key, _type: "dkimResult" }, dkimResult) - ` - - while (expectedCursor.hasMore) { - const tempDkimResult = await expectedCursor.next() - dkimResultKeys.push(tempDkimResult._key) - expectedDkimResults.push(tempDkimResult) - } - - const loader = loadDkimResultByKey({ query, i18n }) - const dkimResults = await loader.loadMany(dkimResultKeys) - expect(dkimResults).toEqual(expectedDkimResults) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadDkimResultByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find DKIM result(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDkimResultByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadDkimResultByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find DKIM result(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDkimResultByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadDkimResultByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDkimResultByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadDkimResultByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDkimResultByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-dkim-results-connections-by-dkim-id.test.js b/api-js/src/email-scan/loaders/__tests__/load-dkim-results-connections-by-dkim-id.test.js deleted file mode 100644 index 7d45e2b248..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-dkim-results-connections-by-dkim-id.test.js +++ /dev/null @@ -1,1362 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadDkimResultConnectionsByDkimId, - loadDkimResultByKey, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load dkim results connection function', () => { - let query, - drop, - truncate, - collections, - user, - dkimScan, - dkimResult1, - dkimResult2, - i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - dkimResult1 = await collections.dkimResults.save({}) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - dkimScan = await collections.dkim.save({ - timestamp: '2020-10-02T12:43:39Z', - }) - dkimResult2 = await collections.dkimResults.save({}) - await collections.dkimToDkimResults.save({ - _from: dkimScan._id, - _to: dkimResult1._id, - }) - await collections.dkimToDkimResults.save({ - _from: dkimScan._id, - _to: dkimResult2._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('using after cursor', () => { - it('returns dkim result(s) after a given node id', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimResultLoader = loadDkimResultByKey({ query }) - const expectedDkimResults = await dkimResultLoader.loadMany([ - dkimResult1._key, - dkimResult2._key, - ]) - - expectedDkimResults[0].id = expectedDkimResults[0]._key - expectedDkimResults[0].dkimId = dkimScan._id - - expectedDkimResults[1].id = expectedDkimResults[1]._key - expectedDkimResults[1].dkimId = dkimScan._id - - const connectionArgs = { - first: 5, - after: toGlobalId('dkimResult', expectedDkimResults[0]._key), - } - - const dkimResults = await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResults[1]._key), - node: { - ...expectedDkimResults[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResults[1]._key), - endCursor: toGlobalId('dkimResult', expectedDkimResults[1]._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dkim result(s) before a given node id', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimResultLoader = loadDkimResultByKey({ query }) - const expectedDkimResults = await dkimResultLoader.loadMany([ - dkimResult1._key, - dkimResult2._key, - ]) - - expectedDkimResults[0].id = expectedDkimResults[0]._key - expectedDkimResults[0].dkimId = dkimScan._id - - expectedDkimResults[1].id = expectedDkimResults[1]._key - expectedDkimResults[1].dkimId = dkimScan._id - - const connectionArgs = { - first: 5, - before: toGlobalId('dkimResult', expectedDkimResults[1]._key), - } - - const dkimResults = await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResults[0]._key), - node: { - ...expectedDkimResults[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dkimResult', expectedDkimResults[0]._key), - endCursor: toGlobalId('dkimResult', expectedDkimResults[0]._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimResultLoader = loadDkimResultByKey({ query }) - const expectedDkimResults = await dkimResultLoader.loadMany([ - dkimResult1._key, - dkimResult2._key, - ]) - - expectedDkimResults[0].id = expectedDkimResults[0]._key - expectedDkimResults[0].dkimId = dkimScan._id - - expectedDkimResults[1].id = expectedDkimResults[1]._key - expectedDkimResults[1].dkimId = dkimScan._id - - const connectionArgs = { - first: 1, - } - - const dkimResults = await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResults[0]._key), - node: { - ...expectedDkimResults[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dkimResult', expectedDkimResults[0]._key), - endCursor: toGlobalId('dkimResult', expectedDkimResults[0]._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimResultLoader = loadDkimResultByKey({ query }) - const expectedDkimResults = await dkimResultLoader.loadMany([ - dkimResult1._key, - dkimResult2._key, - ]) - - expectedDkimResults[0].id = expectedDkimResults[0]._key - expectedDkimResults[0].dkimId = dkimScan._id - - expectedDkimResults[1].id = expectedDkimResults[1]._key - expectedDkimResults[1].dkimId = dkimScan._id - - const connectionArgs = { - last: 1, - } - - const dkimResults = await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResults[1]._key), - node: { - ...expectedDkimResults[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResults[1]._key), - endCursor: toGlobalId('dkimResult', expectedDkimResults[1]._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('using the orderBy argument', () => { - let dkimResultOne, dkimResultTwo, dkimResultThree - beforeEach(async () => { - await truncate() - dkimResultOne = await collections.dkimResults.save({ - selector: 'a.selector.key', - record: 'a.record', - keyLength: 1, - }) - dkimResultTwo = await collections.dkimResults.save({ - selector: 'b.selector.key', - record: 'b.record', - keyLength: 2, - }) - dkimResultThree = await collections.dkimResults.save({ - selector: 'c.selector.key', - record: 'c.record', - keyLength: 3, - }) - await collections.dkimToDkimResults.save({ - _from: dkimScan._id, - _to: dkimResultOne._id, - }) - await collections.dkimToDkimResults.save({ - _from: dkimScan._id, - _to: dkimResultTwo._id, - }) - await collections.dkimToDkimResults.save({ - _from: dkimScan._id, - _to: dkimResultThree._id, - }) - }) - describe('ordering on SELECTOR', () => { - describe('direction set to ASC', () => { - it('returns the dkim result', async () => { - const loader = loadDkimResultByKey({ - query, - userKey: user._key, - i18n, - }) - const expectedDkimResult = await loader.load(dkimResultTwo._key) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - dkimId: dkimScan._id, - first: 5, - after: toGlobalId('dkimResult', dkimResultOne._key), - before: toGlobalId('dkimResult', dkimResultThree._key), - orderBy: { - field: 'selector', - direction: 'ASC', - }, - } - - const dkimResults = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResult._key), - node: { - dkimId: dkimScan._id, - ...expectedDkimResult, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResult._key), - endCursor: toGlobalId('dkimResult', expectedDkimResult._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('direction set to DESC', () => { - it('returns the dkim result', async () => { - const loader = loadDkimResultByKey({ - query, - userKey: user._key, - i18n, - }) - const expectedDkimResult = await loader.load(dkimResultTwo._key) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - dkimId: dkimScan._id, - first: 5, - after: toGlobalId('dkimResult', dkimResultThree._key), - before: toGlobalId('dkimResult', dkimResultOne._key), - orderBy: { - field: 'selector', - direction: 'DESC', - }, - } - - const dkimResults = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResult._key), - node: { - dkimId: dkimScan._id, - ...expectedDkimResult, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResult._key), - endCursor: toGlobalId('dkimResult', expectedDkimResult._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on RECORD', () => { - describe('direction set to ASC', () => { - it('returns the dkim result', async () => { - const loader = loadDkimResultByKey({ - query, - userKey: user._key, - i18n, - }) - const expectedDkimResult = await loader.load(dkimResultTwo._key) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - dkimId: dkimScan._id, - first: 5, - after: toGlobalId('dkimResult', dkimResultOne._key), - before: toGlobalId('dkimResult', dkimResultThree._key), - orderBy: { - field: 'record', - direction: 'ASC', - }, - } - - const dkimResults = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResult._key), - node: { - dkimId: dkimScan._id, - ...expectedDkimResult, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResult._key), - endCursor: toGlobalId('dkimResult', expectedDkimResult._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('direction set to DESC', () => { - it('returns the dkim result', async () => { - const loader = loadDkimResultByKey({ - query, - userKey: user._key, - i18n, - }) - const expectedDkimResult = await loader.load(dkimResultTwo._key) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - dkimId: dkimScan._id, - first: 5, - after: toGlobalId('dkimResult', dkimResultThree._key), - before: toGlobalId('dkimResult', dkimResultOne._key), - orderBy: { - field: 'record', - direction: 'DESC', - }, - } - - const dkimResults = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResult._key), - node: { - dkimId: dkimScan._id, - ...expectedDkimResult, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResult._key), - endCursor: toGlobalId('dkimResult', expectedDkimResult._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on KEY_LENGTH', () => { - describe('direction set to ASC', () => { - it('returns the dkim result', async () => { - const loader = loadDkimResultByKey({ - query, - userKey: user._key, - i18n, - }) - const expectedDkimResult = await loader.load(dkimResultTwo._key) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - dkimId: dkimScan._id, - first: 5, - after: toGlobalId('dkimResult', dkimResultOne._key), - before: toGlobalId('dkimResult', dkimResultThree._key), - orderBy: { - field: 'key-length', - direction: 'ASC', - }, - } - - const dkimResults = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResult._key), - node: { - dkimId: dkimScan._id, - ...expectedDkimResult, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResult._key), - endCursor: toGlobalId('dkimResult', expectedDkimResult._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - describe('direction set to DESC', () => { - it('returns the dkim result', async () => { - const loader = loadDkimResultByKey({ - query, - userKey: user._key, - i18n, - }) - const expectedDkimResult = await loader.load(dkimResultTwo._key) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - dkimId: dkimScan._id, - first: 5, - after: toGlobalId('dkimResult', dkimResultThree._key), - before: toGlobalId('dkimResult', dkimResultOne._key), - orderBy: { - field: 'key-length', - direction: 'DESC', - }, - } - - const dkimResults = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dkimResult', expectedDkimResult._key), - node: { - dkimId: dkimScan._id, - ...expectedDkimResult, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dkimResult', expectedDkimResult._key), - endCursor: toGlobalId('dkimResult', expectedDkimResult._key), - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dkim results are found', () => { - it('returns an empty structure', async () => { - await truncate() - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - const dkimResults = await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dkimResults).toEqual(expectedStructure) - }) - }) - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `DKIMResults` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `DKIMResults` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 1000 records on the `DKIMResults` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 500 records on the `DKIMResults` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load DKIM result(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get dkim result information for ${dkimScan._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load DKIM result(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get dkim result information for ${dkimScan._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `DKIMResults` ne peut être inférieur à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `DKIMResults` ne peut être inférieur à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 1000 enregistrements sur la connexion `DKIMResults` dépasse la limite `first` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 500 enregistrements sur la connexion `DKIMResults` dépasse la limite `last` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDkimResultConnectionsByDkimId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get dkim result information for ${dkimScan._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDkimResultConnectionsByDkimId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimId: dkimScan._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get dkim result information for ${dkimScan._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-dmarc-by-key.test.js b/api-js/src/email-scan/loaders/__tests__/load-dmarc-by-key.test.js deleted file mode 100644 index 7586653170..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-dmarc-by-key.test.js +++ /dev/null @@ -1,216 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadDmarcByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadDmarcByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.dmarc.save({}) - await collections.dmarc.save({}) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single dmarc scan', async () => { - const expectedCursor = await query` - FOR dmarcScan IN dmarc - SORT dmarcScan._key ASC LIMIT 1 - RETURN MERGE({ id: dmarcScan._key, _type: "dmarc" }, dmarcScan) - ` - const expectedDmarc = await expectedCursor.next() - - const loader = await loadDmarcByKey({ query, i18n }) - const dmarc = await loader.load(expectedDmarc._key) - - expect(dmarc).toEqual(expectedDmarc) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dmarc scans', async () => { - const dmarcKeys = [] - const expectedDkimScans = [] - const expectedCursor = await query` - FOR dmarcScan IN dmarc - RETURN MERGE({ id: dmarcScan._key, _type: "dmarc" }, dmarcScan) - ` - - while (expectedCursor.hasMore) { - const tempDmarc = await expectedCursor.next() - dmarcKeys.push(tempDmarc._key) - expectedDkimScans.push(tempDmarc) - } - - const loader = await loadDmarcByKey({ query, i18n }) - const dmarcScans = await loader.loadMany(dmarcKeys) - - expect(dmarcScans).toEqual(expectedDkimScans) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadDmarcByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find DMARC scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDmarcByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadDmarcByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find DMARC scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDmarcByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadDmarcByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDmarcByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadDmarcByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDmarcByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-dmarc-connections-by-domain-id.test.js b/api-js/src/email-scan/loaders/__tests__/load-dmarc-connections-by-domain-id.test.js deleted file mode 100644 index 1307ef952e..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-dmarc-connections-by-domain-id.test.js +++ /dev/null @@ -1,1723 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { loadDmarcConnectionsByDomainId, loadDmarcByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load dmarc connection function', () => { - let query, - drop, - truncate, - collections, - user, - domain, - dmarcScan1, - dmarcScan2, - i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - dmarcScan1 = await collections.dmarc.save({ - timestamp: '2020-10-02T12:43:39Z', - }) - dmarcScan2 = await collections.dmarc.save({ - timestamp: '2020-10-03T12:43:39Z', - }) - await collections.domainsDMARC.save({ - _to: dmarcScan1._id, - _from: domain._id, - }) - await collections.domainsDMARC.save({ - _to: dmarcScan2._id, - _from: domain._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('using after cursor', () => { - it('returns dmarc scan(s) after a given node id', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dkimLoader.loadMany([ - dmarcScan1._key, - dmarcScan2._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - expectedDmarcScans[1].id = expectedDmarcScans[1]._key - expectedDmarcScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - after: toGlobalId('dmarc', expectedDmarcScans[0]._key), - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - node: { - ...expectedDmarcScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dmarc scan(s) before a given node id', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dkimLoader.loadMany([ - dmarcScan1._key, - dmarcScan2._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - expectedDmarcScans[1].id = expectedDmarcScans[1]._key - expectedDmarcScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - before: toGlobalId('dmarc', expectedDmarcScans[1]._key), - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - node: { - ...expectedDmarcScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dkimLoader.loadMany([ - dmarcScan1._key, - dmarcScan2._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - expectedDmarcScans[1].id = expectedDmarcScans[1]._key - expectedDmarcScans[1].domainId = domain._id - - const connectionArgs = { - first: 1, - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - node: { - ...expectedDmarcScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dkimLoader.loadMany([ - dmarcScan1._key, - dmarcScan2._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - expectedDmarcScans[1].id = expectedDmarcScans[1]._key - expectedDmarcScans[1].domainId = domain._id - - const connectionArgs = { - last: 1, - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - node: { - ...expectedDmarcScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('use date filters', () => { - let dmarcScan3 - beforeEach(async () => { - dmarcScan3 = await collections.dmarc.save({ - timestamp: '2020-10-04T12:43:39Z', - }) - await collections.domainsDMARC.save({ - _to: dmarcScan3._id, - _from: domain._id, - }) - }) - describe('using start date filter', () => { - it('returns dkim scans at and after the start date', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dmarcLoader.loadMany([ - dmarcScan2._key, - dmarcScan3._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - expectedDmarcScans[1].id = expectedDmarcScans[1]._key - expectedDmarcScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03', - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - node: { - ...expectedDmarcScans[0], - }, - }, - { - cursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - node: { - ...expectedDmarcScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('using end date filter', () => { - it('returns dkim scans at and before the end date', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dmarcLoader.loadMany([ - dmarcScan1._key, - dmarcScan2._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - expectedDmarcScans[1].id = expectedDmarcScans[1]._key - expectedDmarcScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - endDate: '2020-10-03T13:50:00Z', - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - node: { - ...expectedDmarcScans[0], - }, - }, - { - cursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - node: { - ...expectedDmarcScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[1]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('using start and end date filters', () => { - it('returns dkim scan on a specific date', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcLoader = loadDmarcByKey({ query }) - const expectedDmarcScans = await dmarcLoader.loadMany([ - dmarcScan2._key, - ]) - - expectedDmarcScans[0].id = expectedDmarcScans[0]._key - expectedDmarcScans[0].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03T00:00:00Z', - endDate: '2020-10-03T23:59:59Z', - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - node: { - ...expectedDmarcScans[0], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - endCursor: toGlobalId('dmarc', expectedDmarcScans[0]._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - describe('using orderBy field', () => { - let dmarcScanOne, dmarcScanTwo, dmarcScanThree - beforeEach(async () => { - await truncate() - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - dmarcScanOne = await collections.dmarc.save({ - timestamp: '2021-01-26 23:24:34.506578Z', - record: 'a', - pPolicy: 'a', - spPolicy: 'a', - pct: 1, - }) - dmarcScanTwo = await collections.dmarc.save({ - timestamp: '2021-01-27 23:24:34.506578Z', - record: 'b', - pPolicy: 'b', - spPolicy: 'b', - pct: 2, - }) - dmarcScanThree = await collections.dmarc.save({ - timestamp: '2021-01-28 23:24:34.506578Z', - record: 'c', - pPolicy: 'c', - spPolicy: 'c', - pct: 3, - }) - await collections.domainsDMARC.save({ - _to: dmarcScanOne._id, - _from: domain._id, - }) - await collections.domainsDMARC.save({ - _to: dmarcScanTwo._id, - _from: domain._id, - }) - await collections.domainsDMARC.save({ - _to: dmarcScanThree._id, - _from: domain._id, - }) - }) - describe('ordering on TIMESTAMP', () => { - describe('direction is set to ASC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanOne._key), - before: toGlobalId('dkim', dmarcScanThree._key), - orderBy: { - field: 'timestamp', - direction: 'ASC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanThree._key), - before: toGlobalId('dkim', dmarcScanOne._key), - orderBy: { - field: 'timestamp', - direction: 'DESC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on RECORD', () => { - describe('direction is set to ASC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanOne._key), - before: toGlobalId('dkim', dmarcScanThree._key), - orderBy: { - field: 'record', - direction: 'ASC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanThree._key), - before: toGlobalId('dkim', dmarcScanOne._key), - orderBy: { - field: 'record', - direction: 'DESC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on P_POLICY', () => { - describe('direction is set to ASC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanOne._key), - before: toGlobalId('dkim', dmarcScanThree._key), - orderBy: { - field: 'p-policy', - direction: 'ASC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanThree._key), - before: toGlobalId('dkim', dmarcScanOne._key), - orderBy: { - field: 'p-policy', - direction: 'DESC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on SP_POLICY', () => { - describe('direction is set to ASC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanOne._key), - before: toGlobalId('dkim', dmarcScanThree._key), - orderBy: { - field: 'sp-policy', - direction: 'ASC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanThree._key), - before: toGlobalId('dkim', dmarcScanOne._key), - orderBy: { - field: 'sp-policy', - direction: 'DESC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on PCT', () => { - describe('direction is set to ASC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanOne._key), - before: toGlobalId('dkim', dmarcScanThree._key), - orderBy: { - field: 'pct', - direction: 'ASC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns the dmarc scan', async () => { - const loader = loadDmarcByKey({ query, userKey: user._key, i18n }) - const expectedDmarcScan = await loader.load(dmarcScanTwo._key) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('dkim', dmarcScanThree._key), - before: toGlobalId('dkim', dmarcScanOne._key), - orderBy: { - field: 'pct', - direction: 'DESC', - }, - } - - const dmarcScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('dmarc', expectedDmarcScan._key), - node: { - domainId: domain._id, - ...expectedDmarcScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('dmarc', expectedDmarcScan._key), - endCursor: toGlobalId('dmarc', expectedDmarcScan._key), - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dmarc scans are found', () => { - it('returns an empty structure', async () => { - await truncate() - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - const dmarcScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dmarcScans).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('first and last arguments are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `DMARC` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 2, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `DMARC` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are set below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `DMARC` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -2, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `DMARC` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are set above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 1000 records on the `DMARC` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 200, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 200 records on the `DMARC` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 200 for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get dmarc information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get dmarc information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('first and last arguments are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 2, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are set below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `DMARC` ne peut être inférieur à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -2, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `DMARC` ne peut être inférieur à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are set above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 1000 enregistrements sur la connexion `DMARC` dépasse la limite `first` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 200, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 200 enregistrements sur la connexion `DMARC` dépasse la limite `last` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 200 for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDmarcConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get dmarc information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDmarcConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get dmarc information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-spf-by-key.test.js b/api-js/src/email-scan/loaders/__tests__/load-spf-by-key.test.js deleted file mode 100644 index 0326cb4f0c..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-spf-by-key.test.js +++ /dev/null @@ -1,214 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadSpfByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadSpfByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.spf.save({}) - await collections.spf.save({}) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single spf scan', async () => { - const expectedCursor = await query` - FOR spfScan IN spf - SORT spfScan._key ASC LIMIT 1 - RETURN MERGE({ id: spfScan._key, _type: "spf" }, spfScan) - ` - const expectedSpf = await expectedCursor.next() - - const loader = loadSpfByKey({ query, i18n }) - const spf = await loader.load(expectedSpf._key) - - expect(spf).toEqual(expectedSpf) - }) - }) - describe('given multiple ids', () => { - it('returns multiple spf scans', async () => { - const spfKeys = [] - const expectedSpfScans = [] - const expectedCursor = await query` - FOR spfScan IN spf - RETURN MERGE({ id: spfScan._key, _type: "spf" }, spfScan) - ` - - while (expectedCursor.hasMore) { - const tempSpf = await expectedCursor.next() - spfKeys.push(tempSpf._key) - expectedSpfScans.push(tempSpf) - } - - const loader = loadSpfByKey({ query, i18n }) - const dkimScans = await loader.loadMany(spfKeys) - expect(dkimScans).toEqual(expectedSpfScans) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadSpfByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find SPF scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSpfByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadSpfByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find SPF scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSpfByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadSpfByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSpfByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadSpfByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSpfByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js b/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js deleted file mode 100644 index 9337e63628..0000000000 --- a/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js +++ /dev/null @@ -1,1613 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { loadSpfConnectionsByDomainId, loadSpfByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load spf connection function', () => { - let query, drop, truncate, collections, user, domain, spfScan1, spfScan2, i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - spfScan1 = await collections.spf.save({ - timestamp: '2020-10-02T12:43:39Z', - }) - spfScan2 = await collections.spf.save({ - timestamp: '2020-10-03T12:43:39Z', - }) - await collections.domainsSPF.save({ - _to: spfScan1._id, - _from: domain._id, - }) - await collections.domainsSPF.save({ - _to: spfScan2._id, - _from: domain._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('using after cursor', () => { - it('returns spf scan(s) after a given node id', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([ - spfScan1._key, - spfScan2._key, - ]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[1].id = expectedSpfScans[1]._key - - expectedSpfScans[0].domainId = domain._id - expectedSpfScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - after: toGlobalId('spf', expectedSpfScans[0]._key), - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[1]._key), - node: { - ...expectedSpfScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScans[1]._key), - endCursor: toGlobalId('spf', expectedSpfScans[1]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns spf scan(s) before a given node id', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([ - spfScan1._key, - spfScan2._key, - ]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[1].id = expectedSpfScans[1]._key - - expectedSpfScans[0].domainId = domain._id - expectedSpfScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - before: toGlobalId('spf', expectedSpfScans[1]._key), - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[0]._key), - node: { - ...expectedSpfScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('spf', expectedSpfScans[0]._key), - endCursor: toGlobalId('spf', expectedSpfScans[0]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([ - spfScan1._key, - spfScan2._key, - ]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[1].id = expectedSpfScans[1]._key - - expectedSpfScans[0].domainId = domain._id - expectedSpfScans[1].domainId = domain._id - - const connectionArgs = { - first: 1, - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[0]._key), - node: { - ...expectedSpfScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('spf', expectedSpfScans[0]._key), - endCursor: toGlobalId('spf', expectedSpfScans[0]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([ - spfScan1._key, - spfScan2._key, - ]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[1].id = expectedSpfScans[1]._key - - expectedSpfScans[0].domainId = domain._id - expectedSpfScans[1].domainId = domain._id - - const connectionArgs = { - last: 1, - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[1]._key), - node: { - ...expectedSpfScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScans[1]._key), - endCursor: toGlobalId('spf', expectedSpfScans[1]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('using date filters', () => { - let spfScan3 - beforeEach(async () => { - spfScan3 = await collections.spf.save({ - timestamp: '2020-10-04T12:43:39Z', - }) - await collections.domainsSPF.save({ - _to: spfScan3._id, - _from: domain._id, - }) - }) - describe('using start date filter', () => { - it('returns spf scans at and after the start date', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([ - spfScan2._key, - spfScan3._key, - ]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[1].id = expectedSpfScans[1]._key - - expectedSpfScans[0].domainId = domain._id - expectedSpfScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03', - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[0]._key), - node: { - ...expectedSpfScans[0], - }, - }, - { - cursor: toGlobalId('spf', expectedSpfScans[1]._key), - node: { - ...expectedSpfScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScans[0]._key), - endCursor: toGlobalId('spf', expectedSpfScans[1]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('using end date filter', () => { - it('returns spf scans at and before the end date', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([ - spfScan1._key, - spfScan2._key, - ]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[1].id = expectedSpfScans[1]._key - - expectedSpfScans[0].domainId = domain._id - expectedSpfScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - endDate: '2020-10-03T13:50:00Z', - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[0]._key), - node: { - ...expectedSpfScans[0], - }, - }, - { - cursor: toGlobalId('spf', expectedSpfScans[1]._key), - node: { - ...expectedSpfScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('spf', expectedSpfScans[0]._key), - endCursor: toGlobalId('spf', expectedSpfScans[1]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('using start and end filters', () => { - it('returns a scan on a specific date', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfLoader = loadSpfByKey({ query }) - const expectedSpfScans = await spfLoader.loadMany([spfScan2._key]) - - expectedSpfScans[0].id = expectedSpfScans[0]._key - expectedSpfScans[0].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03T00:00:00Z', - endDate: '2020-10-03T23:59:59Z', - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScans[0]._key), - node: { - ...expectedSpfScans[0], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScans[0]._key), - endCursor: toGlobalId('spf', expectedSpfScans[0]._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - }) - describe('using orderBy field', () => { - let spfOne, spfTwo, spfThree - beforeEach(async () => { - await truncate() - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - }) - spfOne = await collections.spf.save({ - lookups: 1, - record: 'a', - spfDefault: 'a', - timestamp: '2021-01-26 23:29:21.219962', - }) - spfTwo = await collections.spf.save({ - lookups: 2, - record: 'b', - spfDefault: 'b', - timestamp: '2021-01-27 23:29:21.219962', - }) - spfThree = await collections.spf.save({ - lookups: 3, - record: 'c', - spfDefault: 'c', - timestamp: '2021-01-28 23:29:21.219962', - }) - await collections.domainsSPF.save({ - _to: spfOne._id, - _from: domain._id, - }) - await collections.domainsSPF.save({ - _to: spfTwo._id, - _from: domain._id, - }) - await collections.domainsSPF.save({ - _to: spfThree._id, - _from: domain._id, - }) - }) - describe('ordering on TIMESTAMP', () => { - describe('direction is set to ASC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfOne._key), - before: toGlobalId('spf', spfThree._key), - orderBy: { - field: 'timestamp', - direction: 'ASC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfThree._key), - before: toGlobalId('spf', spfOne._key), - orderBy: { - field: 'timestamp', - direction: 'DESC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on LOOKUPS', () => { - describe('direction is set to ASC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfOne._key), - before: toGlobalId('spf', spfThree._key), - orderBy: { - field: 'lookups', - direction: 'ASC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfThree._key), - before: toGlobalId('spf', spfOne._key), - orderBy: { - field: 'lookups', - direction: 'DESC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on RECORD', () => { - describe('direction is set to ASC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfOne._key), - before: toGlobalId('spf', spfThree._key), - orderBy: { - field: 'record', - direction: 'ASC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfThree._key), - before: toGlobalId('spf', spfOne._key), - orderBy: { - field: 'record', - direction: 'DESC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on SPF_DEFAULT', () => { - describe('direction is set to ASC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfOne._key), - before: toGlobalId('spf', spfThree._key), - orderBy: { - field: 'spf-default', - direction: 'ASC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns spf scan', async () => { - const loader = loadSpfByKey({ query, userKey: user._key, i18n }) - const expectedSpfScan = await loader.load(spfTwo._key) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('spf', spfThree._key), - before: toGlobalId('spf', spfOne._key), - orderBy: { - field: 'spf-default', - direction: 'DESC', - }, - } - - const spfScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('spf', expectedSpfScan._key), - node: { - domainId: domain._id, - ...expectedSpfScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('spf', expectedSpfScan._key), - endCursor: toGlobalId('spf', expectedSpfScan._key), - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no spf scans are found', () => { - it('returns an empty structure', async () => { - await truncate() - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - const spfScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(spfScans).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('first and last arguments are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `SPF` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `SPF` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are set below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `SPF` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `SPF` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are set above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 1000 records on the `SPF` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 500 records on the `SPF` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load SPF scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get spf information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load SPF scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get spf information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a unsuccessful load', () => { - describe('first and last arguments are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are set below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `SPF` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `SPF` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are set above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 1000 enregistrements sur la connexion `SPF` dépasse la limite `first` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 500 enregistrements sur la connexion `SPF` dépasse la limite `last` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSpfConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get spf information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSpfConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get spf information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/loaders/index.js b/api-js/src/email-scan/loaders/index.js deleted file mode 100644 index 545cdef20b..0000000000 --- a/api-js/src/email-scan/loaders/index.js +++ /dev/null @@ -1,8 +0,0 @@ -export * from './load-dkim-by-key' -export * from './load-dkim-connections-by-domain-id' -export * from './load-dkim-result-by-key' -export * from './load-dkim-results-connections-by-dkim-id' -export * from './load-dmarc-by-key' -export * from './load-dmarc-connections-by-domain-id' -export * from './load-spf-by-key' -export * from './load-spf-connections-by-domain-id' diff --git a/api-js/src/email-scan/loaders/load-dkim-by-key.js b/api-js/src/email-scan/loaders/load-dkim-by-key.js deleted file mode 100644 index 5db0b8b16f..0000000000 --- a/api-js/src/email-scan/loaders/load-dkim-by-key.js +++ /dev/null @@ -1,35 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadDkimByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - - try { - cursor = await query` - WITH dkim - FOR dkimScan IN dkim - FILTER dkimScan._key IN ${keys} - RETURN MERGE({ id: dkimScan._key, _type: "dkim" }, dkimScan) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadDkimByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to find DKIM scan(s). Please try again.`)) - } - - const dkimMap = {} - try { - await cursor.forEach((dkimScan) => { - dkimMap[dkimScan._key] = dkimScan - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadDkimByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to find DKIM scan(s). Please try again.`)) - } - - return keys.map((key) => dkimMap[key]) - }) diff --git a/api-js/src/email-scan/loaders/load-dkim-connections-by-domain-id.js b/api-js/src/email-scan/loaders/load-dkim-connections-by-domain-id.js deleted file mode 100644 index 248c73f368..0000000000 --- a/api-js/src/email-scan/loaders/load-dkim-connections-by-domain-id.js +++ /dev/null @@ -1,323 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDkimConnectionsByDomainId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ - domainId, - startDate, - endDate, - after, - before, - first, - last, - orderBy, - }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(dkimScan._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dkim, ${afterId})` - - let dkimField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - documentField = aql`afterVar.timestamp` - dkimField = aql`dkimScan.timestamp` - } - - afterTemplate = aql` - FILTER ${dkimField} ${afterTemplateDirection} ${documentField} - OR (${dkimField} == ${documentField} - AND TO_NUMBER(dkimScan._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(dkimScan._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dkim, ${beforeId})` - - let dkimField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - documentField = aql`beforeVar.timestamp` - dkimField = aql`dkimScan.timestamp` - } - - beforeTemplate = aql` - FILTER ${dkimField} ${beforeTemplateDirection} ${documentField} - OR (${dkimField} == ${documentField} - AND TO_NUMBER(dkimScan._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let startDateTemplate = aql`` - if (typeof startDate !== 'undefined') { - startDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(dkimScan.timestamp), - "%y-%m-%d" - ) >= - DATE_FORMAT( - DATE_TIMESTAMP(${startDate}), - "%y-%m-%d" - ) - ` - } - - let endDateTemplate = aql`` - if (typeof endDate !== 'undefined') { - endDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(dkimScan.timestamp), - "%y-%m-%d" - ) <= - DATE_FORMAT( - DATE_TIMESTAMP(${endDate}), - "%y-%m-%d" - ) - ` - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`DKIM\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`DKIM\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDkimConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`DKIM\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDkimConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Requesting ${amount} records on the \`DKIM\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(dkimScan._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(dkimScan._key) DESC LIMIT TO_NUMBER(${last})` - } else { - console.warn( - `User: ${userKey} tried to have \`first\` and \`last\` arguments set for: loadDkimConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`DKIM\` connection is not supported.`, - ), - ) - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDkimConnectionsByDomainId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(dkimScan._key) > TO_NUMBER(LAST(retrievedDkim)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(dkimScan._key) < TO_NUMBER(FIRST(retrievedDkim)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let dkimField, hasNextPageDocumentField, hasPreviousPageDocumentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - dkimField = aql`dkimScan.timestamp` - hasNextPageDocumentField = aql`LAST(retrievedDkim).timestamp` - hasPreviousPageDocumentField = aql`FIRST(retrievedDkim).timestamp` - } - - hasNextPageFilter = aql` - FILTER ${dkimField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${dkimField} == ${hasNextPageDocumentField} - AND TO_NUMBER(dkimScan._key) > TO_NUMBER(LAST(retrievedDkim)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${dkimField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${dkimField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(dkimScan._key) < TO_NUMBER(FIRST(retrievedDkim)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - sortByField = aql`dkimScan.timestamp ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let requestedDkimInfo - try { - requestedDkimInfo = await query` - WITH dkim, domains, domainsDKIM - LET dkimKeys = ( - FOR v, e IN 1 OUTBOUND ${domainId} domainsDKIM - OPTIONS {bfs: true} - RETURN v._key - ) - - ${afterVar} - ${beforeVar} - - LET retrievedDkim = ( - FOR dkimScan IN dkim - FILTER dkimScan._key IN dkimKeys - ${afterTemplate} - ${beforeTemplate} - ${startDateTemplate} - ${endDateTemplate} - - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: dkimScan._key, _type: "dkim" }, dkimScan) - ) - - LET hasNextPage = (LENGTH( - FOR dkimScan IN dkim - FILTER dkimScan._key IN dkimKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(dkimScan._key) ${sortString} LIMIT 1 - RETURN dkimScan - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR dkimScan IN dkim - FILTER dkimScan._key IN dkimKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(dkimScan._key) ${sortString} LIMIT 1 - RETURN dkimScan - ) > 0 ? true : false) - - RETURN { - "dkimScans": retrievedDkim, - "totalCount": LENGTH(dkimKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDkim)._key, - "endKey": LAST(retrievedDkim)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to get dkim information for ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load DKIM scan(s). Please try again.`)) - } - - let dkimScanInfo - try { - dkimScanInfo = await requestedDkimInfo.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to get dkim information for ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load DKIM scan(s). Please try again.`)) - } - - if (dkimScanInfo.dkimScans.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = dkimScanInfo.dkimScans.map((dkimScan) => { - dkimScan.domainId = domainId - return { - cursor: toGlobalId('dkim', dkimScan._key), - node: dkimScan, - } - }) - - return { - edges, - totalCount: dkimScanInfo.totalCount, - pageInfo: { - hasNextPage: dkimScanInfo.hasNextPage, - hasPreviousPage: dkimScanInfo.hasPreviousPage, - startCursor: toGlobalId('dkim', dkimScanInfo.startKey), - endCursor: toGlobalId('dkim', dkimScanInfo.endKey), - }, - } - } diff --git a/api-js/src/email-scan/loaders/load-dkim-result-by-key.js b/api-js/src/email-scan/loaders/load-dkim-result-by-key.js deleted file mode 100644 index 064730e6ca..0000000000 --- a/api-js/src/email-scan/loaders/load-dkim-result-by-key.js +++ /dev/null @@ -1,39 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadDkimResultByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - - try { - cursor = await query` - WITH dkimResults - FOR dkimResult IN dkimResults - FILTER dkimResult._key IN ${keys} - RETURN MERGE({ id: dkimResult._key, _type: "dkimResult" }, dkimResult) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadDkimResultByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to find DKIM result(s). Please try again.`), - ) - } - - const dkimResultMap = {} - try { - await cursor.forEach((dkimResult) => { - dkimResultMap[dkimResult._key] = dkimResult - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadDkimResultByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to find DKIM result(s). Please try again.`), - ) - } - - return keys.map((key) => dkimResultMap[key]) - }) diff --git a/api-js/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js b/api-js/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js deleted file mode 100644 index 365feb51fa..0000000000 --- a/api-js/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js +++ /dev/null @@ -1,305 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDkimResultConnectionsByDkimId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ dkimId, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(dkimResult._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dkimResults, ${afterId})` - - let dkimResultField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'selector') { - dkimResultField = aql`dkimResult.selector` - documentField = aql`afterVar.selector` - } else if (orderBy.field === 'record') { - dkimResultField = aql`dkimResult.record` - documentField = aql`afterVar.record` - } else if (orderBy.field === 'key-length') { - dkimResultField = aql`dkimResult.keyLength` - documentField = aql`afterVar.keyLength` - } - - afterTemplate = aql` - FILTER ${dkimResultField} ${afterTemplateDirection} ${documentField} - OR (${dkimResultField} == ${documentField} - AND TO_NUMBER(dkimResult._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(dkimResult._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dkimResults, ${beforeId})` - - let dkimResultField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'selector') { - dkimResultField = aql`dkimResult.selector` - documentField = aql`beforeVar.selector` - } else if (orderBy.field === 'record') { - dkimResultField = aql`dkimResult.record` - documentField = aql`beforeVar.record` - } else if (orderBy.field === 'key-length') { - dkimResultField = aql`dkimResult.keyLength` - documentField = aql`beforeVar.keyLength` - } - - beforeTemplate = aql` - FILTER ${dkimResultField} ${beforeTemplateDirection} ${documentField} - OR (${dkimResultField} == ${documentField} - AND TO_NUMBER(dkimResult._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDkimResultConnectionsByDkimId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`DKIMResults\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDkimResultConnectionsByDkimId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`DKIMResults\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDkimResultConnectionsByDkimId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`DKIMResults\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDkimResultConnectionsByDkimId.`, - ) - throw new Error( - i18n._( - t`Requesting ${amount} records on the \`DKIMResults\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(dkimResult._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(dkimResult._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDkimResultConnectionsByDkimId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(dkimResult._key) > TO_NUMBER(LAST(retrievedDkimResults)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(dkimResult._key) < TO_NUMBER(FIRST(retrievedDkimResults)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let dkimResultField, - hasNextPageDocumentField, - hasPreviousPageDocumentField - /* istanbul ignore else */ - if (orderBy.field === 'selector') { - dkimResultField = aql`dkimResult.selector` - hasNextPageDocumentField = aql`LAST(retrievedDkimResults).selector` - hasPreviousPageDocumentField = aql`FIRST(retrievedDkimResults).selector` - } else if (orderBy.field === 'record') { - dkimResultField = aql`dkimResult.record` - hasNextPageDocumentField = aql`LAST(retrievedDkimResults).record` - hasPreviousPageDocumentField = aql`FIRST(retrievedDkimResults).record` - } else if (orderBy.field === 'key-length') { - dkimResultField = aql`dkimResult.keyLength` - hasNextPageDocumentField = aql`LAST(retrievedDkimResults).keyLength` - hasPreviousPageDocumentField = aql`FIRST(retrievedDkimResults).keyLength` - } - - hasNextPageFilter = aql` - FILTER ${dkimResultField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${dkimResultField} == ${hasNextPageDocumentField} - AND TO_NUMBER(dkimResult._key) > TO_NUMBER(LAST(retrievedDkimResults)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${dkimResultField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${dkimResultField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(dkimResult._key) < TO_NUMBER(FIRST(retrievedDkimResults)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'selector') { - sortByField = aql`dkimResult.selector ${orderBy.direction},` - } else if (orderBy.field === 'record') { - sortByField = aql`dkimResult.record ${orderBy.direction},` - } else if (orderBy.field === 'key-length') { - sortByField = aql`dkimResult.keyLength ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let dkimResultsCursor - try { - dkimResultsCursor = await query` - WITH dkim, dkimResults, dkimToDkimResults - LET dkimResultKeys = ( - FOR v, e IN 1 OUTBOUND ${dkimId} dkimToDkimResults - OPTIONS {bfs: true} - RETURN v._key - ) - - ${afterVar} - ${beforeVar} - - LET retrievedDkimResults = ( - FOR dkimResult IN dkimResults - FILTER dkimResult._key IN dkimResultKeys - ${afterTemplate} - ${beforeTemplate} - - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: dkimResult._key, _type: "dkimResult" }, dkimResult) - ) - - LET hasNextPage = (LENGTH( - FOR dkimResult IN dkimResults - FILTER dkimResult._key IN dkimResultKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(dkimResult._key) ${sortString} LIMIT 1 - RETURN dkimResult - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR dkimResult IN dkimResults - FILTER dkimResult._key IN dkimResultKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(dkimResult._key) ${sortString} LIMIT 1 - RETURN dkimResult - ) > 0 ? true : false) - - RETURN { - "dkimResults": retrievedDkimResults, - "totalCount": LENGTH(dkimResultKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDkimResults)._key, - "endKey": LAST(retrievedDkimResults)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to get dkim result information for ${dkimId}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DKIM result(s). Please try again.`), - ) - } - - let dkimResultsInfo - try { - dkimResultsInfo = await dkimResultsCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to get dkim result information for ${dkimId}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DKIM result(s). Please try again.`), - ) - } - - if (dkimResultsInfo.dkimResults.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = dkimResultsInfo.dkimResults.map((dkimResult) => { - dkimResult.dkimId = dkimId - return { - cursor: toGlobalId('dkimResult', dkimResult._key), - node: dkimResult, - } - }) - - return { - edges, - totalCount: dkimResultsInfo.totalCount, - pageInfo: { - hasNextPage: dkimResultsInfo.hasNextPage, - hasPreviousPage: dkimResultsInfo.hasPreviousPage, - startCursor: toGlobalId('dkimResult', dkimResultsInfo.startKey), - endCursor: toGlobalId('dkimResult', dkimResultsInfo.endKey), - }, - } - } diff --git a/api-js/src/email-scan/loaders/load-dmarc-by-key.js b/api-js/src/email-scan/loaders/load-dmarc-by-key.js deleted file mode 100644 index bb8128b91d..0000000000 --- a/api-js/src/email-scan/loaders/load-dmarc-by-key.js +++ /dev/null @@ -1,38 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadDmarcByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - - try { - cursor = await query` - WITH dmarc - FOR dmarcScan IN dmarc - FILTER dmarcScan._key IN ${keys} - RETURN MERGE({ id: dmarcScan._key, _type: "dmarc" }, dmarcScan) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadDmarcByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to find DMARC scan(s). Please try again.`), - ) - } - - const dmarcMap = {} - try { - await cursor.forEach((dmarcScan) => { - dmarcMap[dmarcScan._key] = dmarcScan - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadDmarcByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to find DMARC scan(s). Please try again.`), - ) - } - return keys.map((key) => dmarcMap[key]) - }) diff --git a/api-js/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js b/api-js/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js deleted file mode 100644 index 0c31cc578c..0000000000 --- a/api-js/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js +++ /dev/null @@ -1,366 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDmarcConnectionsByDomainId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ - domainId, - startDate, - endDate, - after, - before, - first, - last, - orderBy, - }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(dmarcScan._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dmarc, ${afterId})` - - let dmarcField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - dmarcField = aql`dmarcScan.timestamp` - documentField = aql`afterVar.timestamp` - } else if (orderBy.field === 'record') { - dmarcField = aql`dmarcScan.record` - documentField = aql`afterVar.record` - } else if (orderBy.field === 'p-policy') { - dmarcField = aql`dmarcScan.pPolicy` - documentField = aql`afterVar.pPolicy` - } else if (orderBy.field === 'sp-policy') { - dmarcField = aql`dmarcScan.spPolicy` - documentField = aql`afterVar.spPolicy` - } else if (orderBy.field === 'pct') { - dmarcField = aql`dmarcScan.pct` - documentField = aql`afterVar.pct` - } - - afterTemplate = aql` - FILTER ${dmarcField} ${afterTemplateDirection} ${documentField} - OR (${dmarcField} == ${documentField} - AND TO_NUMBER(dmarcScan._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(dmarcScan._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dmarc, ${beforeId})` - - let dmarcField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - dmarcField = aql`dmarcScan.timestamp` - documentField = aql`beforeVar.timestamp` - } else if (orderBy.field === 'record') { - dmarcField = aql`dmarcScan.record` - documentField = aql`beforeVar.record` - } else if (orderBy.field === 'p-policy') { - dmarcField = aql`dmarcScan.pPolicy` - documentField = aql`beforeVar.pPolicy` - } else if (orderBy.field === 'sp-policy') { - dmarcField = aql`dmarcScan.spPolicy` - documentField = aql`beforeVar.spPolicy` - } else if (orderBy.field === 'pct') { - dmarcField = aql`dmarcScan.pct` - documentField = aql`beforeVar.pct` - } - - beforeTemplate = aql` - FILTER ${dmarcField} ${beforeTemplateDirection} ${documentField} - OR (${dmarcField} == ${documentField} - AND TO_NUMBER(dmarcScan._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let startDateTemplate = aql`` - if (typeof startDate !== 'undefined') { - startDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(dmarcScan.timestamp), - "%y-%m-%d" - ) >= - DATE_FORMAT( - DATE_TIMESTAMP(${startDate}), - "%y-%m-%d" - ) - ` - } - - let endDateTemplate = aql`` - if (typeof endDate !== 'undefined') { - endDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(dmarcScan.timestamp), - "%y-%m-%d" - ) <= - DATE_FORMAT( - DATE_TIMESTAMP(${endDate}), - "%y-%m-%d" - ) - ` - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`DMARC\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`DMARC\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`DMARC\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Requesting ${amount} records on the \`DMARC\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(dmarcScan._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(dmarcScan._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDmarcConnectionsByDomainId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(dmarcScan._key) > TO_NUMBER(LAST(retrievedDmarcScans)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(dmarcScan._key) < TO_NUMBER(FIRST(retrievedDmarcScans)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let dmarcField, hasNextPageDocumentField, hasPreviousPageDocumentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - dmarcField = aql`dmarcScan.timestamp` - hasNextPageDocumentField = aql`LAST(retrievedDmarcScans).timestamp` - hasPreviousPageDocumentField = aql`FIRST(retrievedDmarcScans).timestamp` - } else if (orderBy.field === 'record') { - dmarcField = aql`dmarcScan.record` - hasNextPageDocumentField = aql`LAST(retrievedDmarcScans).record` - hasPreviousPageDocumentField = aql`FIRST(retrievedDmarcScans).record` - } else if (orderBy.field === 'p-policy') { - dmarcField = aql`dmarcScan.pPolicy` - hasNextPageDocumentField = aql`LAST(retrievedDmarcScans).pPolicy` - hasPreviousPageDocumentField = aql`FIRST(retrievedDmarcScans).pPolicy` - } else if (orderBy.field === 'sp-policy') { - dmarcField = aql`dmarcScan.spPolicy` - hasNextPageDocumentField = aql`LAST(retrievedDmarcScans).spPolicy` - hasPreviousPageDocumentField = aql`FIRST(retrievedDmarcScans).spPolicy` - } else if (orderBy.field === 'pct') { - dmarcField = aql`dmarcScan.pct` - hasNextPageDocumentField = aql`LAST(retrievedDmarcScans).pct` - hasPreviousPageDocumentField = aql`FIRST(retrievedDmarcScans).pct` - } - - hasNextPageFilter = aql` - FILTER ${dmarcField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${dmarcField} == ${hasNextPageDocumentField} - AND TO_NUMBER(dmarcScan._key) > TO_NUMBER(LAST(retrievedDmarcScans)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${dmarcField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${dmarcField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(dmarcScan._key) < TO_NUMBER(FIRST(retrievedDmarcScans)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - sortByField = aql`dmarcScan.timestamp ${orderBy.direction},` - } else if (orderBy.field === 'record') { - sortByField = aql`dmarcScan.record ${orderBy.direction},` - } else if (orderBy.field === 'p-policy') { - sortByField = aql`dmarcScan.pPolicy ${orderBy.direction},` - } else if (orderBy.field === 'sp-policy') { - sortByField = aql`dmarcScan.spPolicy ${orderBy.direction},` - } else if (orderBy.field === 'pct') { - sortByField = aql`dmarcScan.pct ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let dmarcScanInfoCursor - try { - dmarcScanInfoCursor = await query` - WITH dmarc, domains, domainsDMARC - LET dmarcKeys = ( - FOR v, e IN 1 OUTBOUND ${domainId} domainsDMARC - OPTIONS {bfs: true} - RETURN v._key - ) - - ${afterVar} - ${beforeVar} - - LET retrievedDmarcScans = ( - FOR dmarcScan IN dmarc - FILTER dmarcScan._key IN dmarcKeys - ${afterTemplate} - ${beforeTemplate} - ${startDateTemplate} - ${endDateTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: dmarcScan._key, _type: "dmarc" }, dmarcScan) - ) - - LET hasNextPage = (LENGTH( - FOR dmarcScan IN dmarc - FILTER dmarcScan._key IN dmarcKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(dmarcScan._key) ${sortString} LIMIT 1 - RETURN dmarcScan - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR dmarcScan IN dmarc - FILTER dmarcScan._key IN dmarcKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(dmarcScan._key) ${sortString} LIMIT 1 - RETURN dmarcScan - ) > 0 ? true : false) - - RETURN { - "dmarcScans": retrievedDmarcScans, - "totalCount": LENGTH(dmarcKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDmarcScans)._key, - "endKey": LAST(retrievedDmarcScans)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to get dmarc information for ${domainId}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DMARC scan(s). Please try again.`), - ) - } - - let dmarcScanInfo - try { - dmarcScanInfo = await dmarcScanInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to get dmarc information for ${domainId}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DMARC scan(s). Please try again.`), - ) - } - - if (dmarcScanInfo.dmarcScans.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = dmarcScanInfo.dmarcScans.map((dmarcScan) => { - dmarcScan.domainId = domainId - return { - cursor: toGlobalId('dmarc', dmarcScan._key), - node: dmarcScan, - } - }) - - return { - edges, - totalCount: dmarcScanInfo.totalCount, - pageInfo: { - hasNextPage: dmarcScanInfo.hasNextPage, - hasPreviousPage: dmarcScanInfo.hasPreviousPage, - startCursor: toGlobalId('dmarc', dmarcScanInfo.startKey), - endCursor: toGlobalId('dmarc', dmarcScanInfo.endKey), - }, - } - } diff --git a/api-js/src/email-scan/loaders/load-spf-by-key.js b/api-js/src/email-scan/loaders/load-spf-by-key.js deleted file mode 100644 index 93e3ca4098..0000000000 --- a/api-js/src/email-scan/loaders/load-spf-by-key.js +++ /dev/null @@ -1,35 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadSpfByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - - try { - cursor = await query` - WITH spf - FOR spfScan IN spf - FILTER spfScan._key IN ${keys} - RETURN MERGE({ id: spfScan._key, _type: "spf" }, spfScan) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadSpfByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to find SPF scan(s). Please try again.`)) - } - - const spfMap = {} - try { - await cursor.forEach((spfScan) => { - spfMap[spfScan._key] = spfScan - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadSpfByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to find SPF scan(s). Please try again.`)) - } - - return keys.map((key) => spfMap[key]) - }) diff --git a/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js b/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js deleted file mode 100644 index 8db41564d8..0000000000 --- a/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js +++ /dev/null @@ -1,350 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadSpfConnectionsByDomainId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ - domainId, - startDate, - endDate, - after, - before, - first, - last, - orderBy, - }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(spfScan._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(spf, ${afterId})` - - let spfField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - spfField = aql`spfScan.timestamp` - documentField = aql`afterVar.timestamp` - } else if (orderBy.field === 'lookups') { - spfField = aql`spfScan.lookups` - documentField = aql`afterVar.lookups` - } else if (orderBy.field === 'record') { - spfField = aql`spfScan.record` - documentField = aql`afterVar.record` - } else if (orderBy.field === 'spf-default') { - spfField = aql`spfScan.spfDefault` - documentField = aql`afterVar.spfDefault` - } - - afterTemplate = aql` - FILTER ${spfField} ${afterTemplateDirection} ${documentField} - OR (${spfField} == ${documentField} - AND TO_NUMBER(spfScan._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(spfScan._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(spf, ${beforeId})` - - let spfField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - spfField = aql`spfScan.timestamp` - documentField = aql`beforeVar.timestamp` - } else if (orderBy.field === 'lookups') { - spfField = aql`spfScan.lookups` - documentField = aql`beforeVar.lookups` - } else if (orderBy.field === 'record') { - spfField = aql`spfScan.record` - documentField = aql`beforeVar.record` - } else if (orderBy.field === 'spf-default') { - spfField = aql`spfScan.spfDefault` - documentField = aql`beforeVar.spfDefault` - } - - beforeTemplate = aql` - FILTER ${spfField} ${beforeTemplateDirection} ${documentField} - OR (${spfField} == ${documentField} - AND TO_NUMBER(spfScan._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let startDateTemplate = aql`` - if (typeof startDate !== 'undefined') { - startDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(spfScan.timestamp), - "%y-%m-%d" - ) >= - DATE_FORMAT( - DATE_TIMESTAMP(${startDate}), - "%y-%m-%d" - ) - ` - } - - let endDateTemplate = aql`` - if (typeof endDate !== 'undefined') { - endDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(spfScan.timestamp), - "%y-%m-%d" - ) <= - DATE_FORMAT( - DATE_TIMESTAMP(${endDate}), - "%y-%m-%d" - ) - ` - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSpfConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`SPF\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSpfConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`SPF\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSpfConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`SPF\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSpfConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Requesting ${amount} records on the \`SPF\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(spfScan._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(spfScan._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadSpfConnectionsByDomainId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(spfScan._key) > TO_NUMBER(LAST(retrievedSpfScans)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(spfScan._key) < TO_NUMBER(FIRST(retrievedSpfScans)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let spfField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - spfField = aql`spfScan.timestamp` - hasNextPageDocument = aql`LAST(retrievedSpfScans).timestamp` - hasPreviousPageDocument = aql`FIRST(retrievedSpfScans).timestamp` - } else if (orderBy.field === 'lookups') { - spfField = aql`spfScan.lookups` - hasNextPageDocument = aql`LAST(retrievedSpfScans).lookups` - hasPreviousPageDocument = aql`FIRST(retrievedSpfScans).lookups` - } else if (orderBy.field === 'record') { - spfField = aql`spfScan.record` - hasNextPageDocument = aql`LAST(retrievedSpfScans).record` - hasPreviousPageDocument = aql`FIRST(retrievedSpfScans).record` - } else if (orderBy.field === 'spf-default') { - spfField = aql`spfScan.spfDefault` - hasNextPageDocument = aql`LAST(retrievedSpfScans).spfDefault` - hasPreviousPageDocument = aql`FIRST(retrievedSpfScans).spfDefault` - } - - hasNextPageFilter = aql` - FILTER ${spfField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${spfField} == ${hasNextPageDocument} - AND TO_NUMBER(spfScan._key) > TO_NUMBER(LAST(retrievedSpfScans)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${spfField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${spfField} == ${hasPreviousPageDocument} - AND TO_NUMBER(spfScan._key) < TO_NUMBER(FIRST(retrievedSpfScans)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - sortByField = aql`spfScan.timestamp ${orderBy.direction},` - } else if (orderBy.field === 'lookups') { - sortByField = aql`spfScan.lookups ${orderBy.direction},` - } else if (orderBy.field === 'record') { - sortByField = aql`spfScan.record ${orderBy.direction},` - } else if (orderBy.field === 'spf-default') { - sortByField = aql`spfScan.spfDefault ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let spfScanInfoCursor - try { - spfScanInfoCursor = await query` - WITH domains, domainsSPF, spf - LET spfKeys = ( - FOR v, e IN 1 OUTBOUND ${domainId} domainsSPF - OPTIONS {bfs: true} - RETURN v._key - ) - - ${afterVar} - ${beforeVar} - - LET retrievedSpfScans = ( - FOR spfScan IN spf - FILTER spfScan._key IN spfKeys - ${afterTemplate} - ${beforeTemplate} - ${startDateTemplate} - ${endDateTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: spfScan._key, _type: "spf" }, spfScan) - ) - - LET hasNextPage = (LENGTH( - FOR spfScan IN spf - FILTER spfScan._key IN spfKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(spfScan._key) ${sortString} LIMIT 1 - RETURN spfScan - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR spfScan IN spf - FILTER spfScan._key IN spfKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(spfScan._key) ${sortString} LIMIT 1 - RETURN spfScan - ) > 0 ? true : false) - - RETURN { - "spfScans": retrievedSpfScans, - "totalCount": LENGTH(spfKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedSpfScans)._key, - "endKey": LAST(retrievedSpfScans)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to get spf information for ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load SPF scan(s). Please try again.`)) - } - - let spfScanInfo - try { - spfScanInfo = await spfScanInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to get spf information for ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load SPF scan(s). Please try again.`)) - } - - if (spfScanInfo.spfScans.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = spfScanInfo.spfScans.map((spfScan) => { - spfScan.domainId = domainId - return { - cursor: toGlobalId('spf', spfScan._key), - node: spfScan, - } - }) - - return { - edges, - totalCount: spfScanInfo.totalCount, - pageInfo: { - hasNextPage: spfScanInfo.hasNextPage, - hasPreviousPage: spfScanInfo.hasPreviousPage, - startCursor: toGlobalId('spf', spfScanInfo.startKey), - endCursor: toGlobalId('spf', spfScanInfo.endKey), - }, - } - } diff --git a/api-js/src/email-scan/objects/__tests__/dkim-connection.test.js b/api-js/src/email-scan/objects/__tests__/dkim-connection.test.js deleted file mode 100644 index 61bc35b078..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dkim-connection.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { dkimConnection } from '../dkim-connection' - -describe('given the dkim connection object', () => { - describe('testing its field definitions', () => { - it('has a totalCount field', () => { - const demoType = dkimConnection.connectionType.getFields() - - expect(demoType).toHaveProperty('totalCount') - expect(demoType.totalCount.type).toMatchObject(GraphQLInt) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the totalCount resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimConnection.connectionType.getFields() - - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dkim-result-sub.test.js b/api-js/src/email-scan/objects/__tests__/dkim-result-sub.test.js deleted file mode 100644 index 14b9835e41..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dkim-result-sub.test.js +++ /dev/null @@ -1,340 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { databaseOptions } from '../../../../database-options' -import { loadDkimGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { guidanceTagType } from '../../../guidance-tag/objects' -import { dkimResultSubType } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('Given The dkimResultSubType object', () => { - describe('testing its field definitions', () => { - it('has selector field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('selector') - expect(demoType.selector.type).toMatchObject(GraphQLString) - }) - it('has record field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('record') - expect(demoType.record.type).toMatchObject(GraphQLString) - }) - it('has keyLength field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('keyLength') - expect(demoType.keyLength.type).toMatchObject(GraphQLString) - }) - it('has a rawJson field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has negativeGuidanceTags field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has neutralGuidanceTags field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has positiveGuidanceTags field', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the selector resolver', () => { - it('returns the parsed value', () => { - const demoType = dkimResultSubType.getFields() - - expect( - demoType.selector.resolve({ selector: 'selector._dkim1' }), - ).toEqual('selector._dkim1') - }) - }) - describe('testing the record resolver', () => { - it('returns the parsed value', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType.record.resolve({ record: 'txtRecord' })).toEqual( - 'txtRecord', - ) - }) - }) - describe('testing the keyLength resolver', () => { - it('returns the parsed value', () => { - const demoType = dkimResultSubType.getFields() - - expect(demoType.keyLength.resolve({ keyLength: '2048' })).toEqual( - '2048', - ) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimResultSubType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - let query, drop, truncate, collections, dkimGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - dkimGT = await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - en: { - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = dkimResultSubType.getFields() - - const loader = loadDkimGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const negativeTags = ['dkim1'] - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - {}, - { loaders: { loadDkimGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: dkimGT._id, - _key: dkimGT._key, - _rev: dkimGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - ]) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - let query, drop, truncate, collections, dkimGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - dkimGT = await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - en: { - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = dkimResultSubType.getFields() - - const loader = loadDkimGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const neutralTags = ['dkim1'] - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - {}, - { loaders: { loadDkimGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: dkimGT._id, - _key: dkimGT._key, - _rev: dkimGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - ]) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - let query, drop, truncate, collections, dkimGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - dkimGT = await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - en: { - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = dkimResultSubType.getFields() - - const loader = loadDkimGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const positiveTags = ['dkim1'] - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - {}, - { loaders: { loadDkimGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: dkimGT._id, - _key: dkimGT._key, - _rev: dkimGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dkim-results-connection.test.js b/api-js/src/email-scan/objects/__tests__/dkim-results-connection.test.js deleted file mode 100644 index 623744085d..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dkim-results-connection.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { dkimResultConnection } from '../dkim-result-connection' - -describe('given the dkim result connection object', () => { - describe('testing its field definitions', () => { - it('has a totalCount field', () => { - const demoType = dkimResultConnection.connectionType.getFields() - - expect(demoType).toHaveProperty('totalCount') - expect(demoType.totalCount.type).toMatchObject(GraphQLInt) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the totalCount resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimResultConnection.connectionType.getFields() - - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dkim-results.test.js b/api-js/src/email-scan/objects/__tests__/dkim-results.test.js deleted file mode 100644 index 9befe68b4f..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dkim-results.test.js +++ /dev/null @@ -1,385 +0,0 @@ -import { toGlobalId } from 'graphql-relay' -import { GraphQLID, GraphQLNonNull, GraphQLString } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { dkimType, dkimResultType } from '../index' -import { guidanceTagConnection } from '../../../guidance-tag/objects' - -describe('given the dkim result object', () => { - describe('testing its field definitions', () => { - it('has an id field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a dkim field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('dkim') - expect(demoType.dkim.type).toEqual(dkimType) - }) - it('has a selector field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('selector') - expect(demoType.selector.type).toEqual(GraphQLString) - }) - it('has a record field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('record') - expect(demoType.record.type).toMatchObject(GraphQLString) - }) - it('has a keyLength field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('keyLength') - expect(demoType.keyLength.type).toMatchObject(GraphQLString) - }) - it('has a rawJson field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has a guidanceTags field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('guidanceTags') - expect(demoType.guidanceTags.type).toEqual( - guidanceTagConnection.connectionType, - ) - }) - it('has a negativeGuidanceTags field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toEqual( - guidanceTagConnection.connectionType, - ) - }) - it('has a neutralGuidanceTags field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toEqual( - guidanceTagConnection.connectionType, - ) - }) - it('has a positiveGuidanceTags field', () => { - const demoType = dkimResultType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toEqual( - guidanceTagConnection.connectionType, - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the id resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimResultType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('dkimResult', 1), - ) - }) - }) - describe('testing the dkim resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimResultType.getFields() - - const expectedResult = { - _id: 'dkim/1', - _key: '1', - _rev: 'rev', - _type: 'dkim', - id: '1', - timestamp: '2020-10-02T12:43:39Z', - } - - expect( - await demoType.dkim.resolve( - { dkimId: 'dkim/1' }, - {}, - { - loaders: { - loadDkimByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the selector field', () => { - it('returns the resolved value', () => { - const demoType = dkimResultType.getFields() - - expect( - demoType.selector.resolve({ selector: 'selector._dkim1' }), - ).toEqual('selector._dkim1') - }) - }) - describe('testing the record resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimResultType.getFields() - - expect(demoType.record.resolve({ record: 'txtRecord' })).toEqual( - 'txtRecord', - ) - }) - }) - describe('testing the keyLength resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimResultType.getFields() - - expect(demoType.keyLength.resolve({ keyLength: '2048' })).toEqual( - '2048', - ) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimResultType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the guidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimResultType.getFields() - - const guidanceTags = ['dkim1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dkim1'), - node: { - _id: 'dkimGuidanceTags/dkim1', - _key: 'dkim1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dkim1'), - endCursor: toGlobalId('guidanceTags', 'dkim1'), - }, - } - - expect( - await demoType.guidanceTags.resolve( - { guidanceTags }, - { first: 1 }, - { - loaders: { - loadDkimGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimResultType.getFields() - const negativeTags = ['dkim1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dkim1'), - node: { - _id: 'dkimGuidanceTags/dkim1', - _key: 'dkim1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dkim1'), - endCursor: toGlobalId('guidanceTags', 'dkim1'), - }, - } - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - { first: 1 }, - { - loaders: { - loadDkimGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimResultType.getFields() - const neutralTags = ['dkim1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dkim1'), - node: { - _id: 'dkimGuidanceTags/dkim1', - _key: 'dkim1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dkim1'), - endCursor: toGlobalId('guidanceTags', 'dkim1'), - }, - } - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - { first: 1 }, - { - loaders: { - loadDkimGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimResultType.getFields() - const positiveTags = ['dkim1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dkim1'), - node: { - _id: 'dkimGuidanceTags/dkim1', - _key: 'dkim1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dkim1'), - endCursor: toGlobalId('guidanceTags', 'dkim1'), - }, - } - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - { first: 1 }, - { - loaders: { - loadDkimGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dkim-sub.test.js b/api-js/src/email-scan/objects/__tests__/dkim-sub.test.js deleted file mode 100644 index ada0c6bd0d..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dkim-sub.test.js +++ /dev/null @@ -1,190 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLID, GraphQLList } from 'graphql' - -import { databaseOptions } from '../../../../database-options' -import { dkimResultSubType, dkimSubType } from '../index' -import { domainType } from '../../../domain/objects' -import { StatusEnum } from '../../../enums' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the dkimSubType object', () => { - describe('testing its field definitions', () => { - it('has sharedId field', () => { - const demoType = dkimSubType.getFields() - - expect(demoType).toHaveProperty('sharedId') - expect(demoType.sharedId.type).toMatchObject(GraphQLID) - }) - it('has a domain field', () => { - const demoType = dkimSubType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a status field', () => { - const demoType = dkimSubType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(StatusEnum) - }) - it('has results field', () => { - const demoType = dkimSubType.getFields() - - expect(demoType).toHaveProperty('results') - expect(demoType.results.type).toMatchObject( - GraphQLList(dkimResultSubType), - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the sharedId resolver', () => { - it('returns the parsed value', () => { - const demoType = dkimSubType.getFields() - - expect(demoType.sharedId.resolve({ sharedId: 'sharedId' })).toEqual( - 'sharedId', - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimSubType.getFields() - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainKey: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the status resolver', () => { - it('returns the parsed value', () => { - const demoType = dkimSubType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the results resolver', () => { - let drop, truncate, collections, dkimGT - - beforeAll(async () => { - ;({ drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - - beforeEach(async () => { - await truncate() - dkimGT = await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }) - }) - - afterAll(async () => { - await drop() - }) - - it('returns the parsed value', async () => { - const demoType = dkimSubType.getFields() - - const resultObj = [ - { - selector: 'selector._dkim1', - record: 'txtRecord', - keyLength: '2048', - guidanceTags: [ - { - _id: dkimGT._id, - _key: dkimGT._key, - _rev: dkimGT._rev, - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - ], - }, - ] - - expect(demoType.results.resolve({ results: resultObj })).toEqual([ - { - guidanceTags: [ - { - _id: dkimGT._id, - _key: dkimGT._key, - _rev: dkimGT._rev, - guidance: 'Some Interesting Guidance', - id: 'dkim1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dkim1', - tagName: 'DKIM-TAG', - }, - ], - keyLength: '2048', - record: 'txtRecord', - selector: 'selector._dkim1', - }, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dkim.test.js b/api-js/src/email-scan/objects/__tests__/dkim.test.js deleted file mode 100644 index 1b4b7e385c..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dkim.test.js +++ /dev/null @@ -1,130 +0,0 @@ -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { GraphQLDate } from 'graphql-scalars' -import { toGlobalId } from 'graphql-relay' - -import { domainType } from '../../../domain/objects' -import { dkimType } from '../dkim' -import { dkimResultConnection } from '../dkim-result-connection' - -describe('given the dkimType object', () => { - describe('testing its field definitions', () => { - it('has an id field', () => { - const demoType = dkimType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a domain field', () => { - const demoType = dkimType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a timestamp field', () => { - const demoType = dkimType.getFields() - - expect(demoType).toHaveProperty('timestamp') - expect(demoType.timestamp.type).toMatchObject(GraphQLDate) - }) - it('has a results field', () => { - const demoType = dkimType.getFields() - - expect(demoType).toHaveProperty('results') - expect(demoType.results.type).toMatchObject( - dkimResultConnection.connectionType, - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the id resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('dkim', 1)) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimType.getFields() - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainId: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the timestamp resolver', () => { - it('returns the resolved value', () => { - const demoType = dkimType.getFields() - - expect( - demoType.timestamp.resolve({ timestamp: '2020-10-02T12:43:39Z' }), - ).toEqual(new Date('2020-10-02T12:43:39Z')) - }) - }) - describe('testing the results resolver', () => { - it('returns the resolved value', async () => { - const demoType = dkimType.getFields() - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('dkimResult', '1'), - node: { - _id: 'dkimResults/1', - _key: '1', - _rev: 'rev', - _type: 'dkimResult', - id: '1', - dkimId: 'dkimGuidanceTags/dkim1', - selector: 'selector._dkim1', - record: 'txtRecord', - keyLength: '2048', - guidanceTags: ['dkim1'], - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('dkimResult', '1'), - endCursor: toGlobalId('dkimResult', '1'), - }, - } - - await expect( - demoType.results.resolve( - { _id: '1' }, - { first: 1 }, - { - loaders: { - loadDkimResultConnectionsByDkimId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dmarc-connection.test.js b/api-js/src/email-scan/objects/__tests__/dmarc-connection.test.js deleted file mode 100644 index 833ffaa7b1..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dmarc-connection.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { dmarcConnection } from '../index' - -describe('given the dmarc connection object', () => { - describe('testing its field definitions', () => { - it('has a totalCount field', () => { - const demoType = dmarcConnection.connectionType.getFields() - - expect(demoType).toHaveProperty('totalCount') - expect(demoType.totalCount.type).toMatchObject(GraphQLInt) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the totalCount resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcConnection.connectionType.getFields() - - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dmarc-sub.test.js b/api-js/src/email-scan/objects/__tests__/dmarc-sub.test.js deleted file mode 100644 index 26def3cbab..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dmarc-sub.test.js +++ /dev/null @@ -1,417 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList, GraphQLInt, GraphQLID } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { databaseOptions } from '../../../../database-options' -import { loadDmarcGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { guidanceTagType } from '../../../guidance-tag/objects' -import { dmarcSubType } from '../index' -import { domainType } from '../../../domain/objects' -import { StatusEnum } from '../../../enums' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the dmarcSubType object', () => { - describe('testing its field definitions', () => { - it('has sharedId field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('sharedId') - expect(demoType.sharedId.type).toMatchObject(GraphQLID) - }) - it('has a domain field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a status field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(StatusEnum) - }) - it('has record field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('record') - expect(demoType.record.type).toMatchObject(GraphQLString) - }) - it('has pPolicy field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('pPolicy') - expect(demoType.pPolicy.type).toMatchObject(GraphQLString) - }) - it('has spPolicy field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('spPolicy') - expect(demoType.spPolicy.type).toMatchObject(GraphQLString) - }) - it('has pct field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('pct') - expect(demoType.pct.type).toMatchObject(GraphQLInt) - }) - it('has a rawJson field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has negativeGuidanceTags field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has neutralGuidanceTags field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has positiveGuidanceTags field', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the sharedId resolver', () => { - it('returns the parsed value', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType.sharedId.resolve({ sharedId: 'sharedId' })).toEqual( - 'sharedId', - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = dmarcSubType.getFields() - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainKey: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the status resolver', () => { - it('returns the parsed value', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the record resolver', () => { - it('returns the parsed value', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType.record.resolve({ record: 'txtRecord' })).toEqual( - 'txtRecord', - ) - }) - }) - describe('testing the pPolicy resolver', () => { - it('returns the parsed value', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType.pPolicy.resolve({ pPolicy: 'pPolicy' })).toEqual( - 'pPolicy', - ) - }) - }) - describe('testing the spPolicy resolver', () => { - it('returns the parsed value', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType.spPolicy.resolve({ spPolicy: 'spPolicy' })).toEqual( - 'spPolicy', - ) - }) - }) - describe('testing the pct resolver', () => { - it('returns the parsed value', () => { - const demoType = dmarcSubType.getFields() - - expect(demoType.pct.resolve({ pct: 100 })).toEqual(100) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcSubType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - let query, drop, truncate, collections, dmarcGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - dmarcGT = await collections.dmarcGuidanceTags.save({ - _key: 'dmarc1', - en: { - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = dmarcSubType.getFields() - - const loader = loadDmarcGuidanceTagByTagId({ - query, - userKey: 1, - i18n: {}, - language: 'en', - }) - const negativeTags = ['dmarc1'] - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - {}, - { loaders: { loadDmarcGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: dmarcGT._id, - _key: dmarcGT._key, - _rev: dmarcGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - ]) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - let query, drop, truncate, collections, dmarcGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - dmarcGT = await collections.dmarcGuidanceTags.save({ - _key: 'dmarc1', - en: { - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = dmarcSubType.getFields() - - const loader = loadDmarcGuidanceTagByTagId({ - query, - userKey: 1, - i18n: {}, - language: 'en', - }) - const neutralTags = ['dmarc1'] - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - {}, - { loaders: { loadDmarcGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: dmarcGT._id, - _key: dmarcGT._key, - _rev: dmarcGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - ]) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - let query, drop, truncate, collections, dmarcGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - dmarcGT = await collections.dmarcGuidanceTags.save({ - _key: 'dmarc1', - en: { - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = dmarcSubType.getFields() - - const loader = loadDmarcGuidanceTagByTagId({ - query, - userKey: 1, - i18n: {}, - language: 'en', - }) - const positiveTags = ['dmarc1'] - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - {}, - { loaders: { loadDmarcGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: dmarcGT._id, - _key: dmarcGT._key, - _rev: dmarcGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/dmarc.test.js b/api-js/src/email-scan/objects/__tests__/dmarc.test.js deleted file mode 100644 index c70bd167ec..0000000000 --- a/api-js/src/email-scan/objects/__tests__/dmarc.test.js +++ /dev/null @@ -1,411 +0,0 @@ -import { GraphQLNonNull, GraphQLID, GraphQLString, GraphQLInt } from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { GraphQLJSON, GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../../domain/objects' -import { guidanceTagConnection } from '../../../guidance-tag/objects' -import { dmarcType } from '../index' - -describe('given the dmarcType object', () => { - describe('testing its field definitions', () => { - it('has an id field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a domain field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a timestamp field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('timestamp') - expect(demoType.timestamp.type).toMatchObject(GraphQLDate) - }) - it('has a record field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('record') - expect(demoType.record.type).toMatchObject(GraphQLString) - }) - it('has a pPolicy field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('pPolicy') - expect(demoType.pPolicy.type).toEqual(GraphQLString) - }) - it('has a spPolicy field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('spPolicy') - expect(demoType.spPolicy.type).toMatchObject(GraphQLString) - }) - it('has a pct field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('pct') - expect(demoType.pct.type).toMatchObject(GraphQLInt) - }) - it('has a rawJson field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has a guidanceTags field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('guidanceTags') - expect(demoType.guidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a negativeGuidanceTags field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a neutralGuidanceTags field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a positiveGuidanceTags field', () => { - const demoType = dmarcType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the id field resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('dmarc', 1)) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = dmarcType.getFields() - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainId: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the timestamp resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - expect( - demoType.timestamp.resolve({ timestamp: '2020-10-02T12:43:39Z' }), - ).toEqual(new Date('2020-10-02T12:43:39Z')) - }) - }) - describe('testing the record resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - expect(demoType.record.resolve({ record: 'txtRecord' })).toEqual( - 'txtRecord', - ) - }) - }) - describe('testing the pPolicy resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - expect(demoType.pPolicy.resolve({ pPolicy: 'pPolicy' })).toEqual( - 'pPolicy', - ) - }) - }) - describe('testing the spPolicy resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - expect(demoType.spPolicy.resolve({ spPolicy: 'spPolicy' })).toEqual( - 'spPolicy', - ) - }) - }) - describe('testing the pct resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - expect(demoType.pct.resolve({ pct: 100 })).toEqual(100) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = dmarcType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the guidanceTag resolver', () => { - it('returns the resolved value', async () => { - const demoType = dmarcType.getFields() - const guidanceTags = ['dmarc1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dmarc1'), - node: { - _id: 'dmarcGuidanceTags/dmarc1', - _key: 'dmarc1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dmarc1'), - endCursor: toGlobalId('guidanceTags', 'dmarc1'), - }, - } - - await expect( - demoType.guidanceTags.resolve( - { guidanceTags }, - { first: 1 }, - { - loaders: { - loadDmarcGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dmarcType.getFields() - const negativeTags = ['dmarc1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dmarc1'), - node: { - _id: 'dmarcGuidanceTags/dmarc1', - _key: 'dmarc1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dmarc1'), - endCursor: toGlobalId('guidanceTags', 'dmarc1'), - }, - } - - await expect( - demoType.negativeGuidanceTags.resolve( - { negativeTags }, - { first: 1 }, - { - loaders: { - loadDmarcGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dmarcType.getFields() - const neutralTags = ['dmarc1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dmarc1'), - node: { - _id: 'dmarcGuidanceTags/dmarc1', - _key: 'dmarc1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dmarc1'), - endCursor: toGlobalId('guidanceTags', 'dmarc1'), - }, - } - - await expect( - demoType.neutralGuidanceTags.resolve( - { neutralTags }, - { first: 1 }, - { - loaders: { - loadDmarcGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = dmarcType.getFields() - const positiveTags = ['dmarc1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'dmarc1'), - node: { - _id: 'dmarcGuidanceTags/dmarc1', - _key: 'dmarc1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'dmarc1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'dmarc1'), - endCursor: toGlobalId('guidanceTags', 'dmarc1'), - }, - } - - await expect( - demoType.positiveGuidanceTags.resolve( - { positiveTags }, - { first: 1 }, - { - loaders: { - loadDmarcGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/email-scan.test.js b/api-js/src/email-scan/objects/__tests__/email-scan.test.js deleted file mode 100644 index 210d5600d0..0000000000 --- a/api-js/src/email-scan/objects/__tests__/email-scan.test.js +++ /dev/null @@ -1,207 +0,0 @@ -import { toGlobalId } from 'graphql-relay' - -import { domainType } from '../../../domain/objects' -import { - emailScanType, - dkimConnection, - dmarcConnection, - spfConnection, -} from '../index' - -describe('given the email gql object', () => { - describe('testing its field definitions', () => { - it('has a domain field', () => { - const demoType = emailScanType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a dkim field', () => { - const demoType = emailScanType.getFields() - - expect(demoType).toHaveProperty('dkim') - expect(demoType.dkim.type).toMatchObject(dkimConnection.connectionType) - }) - it('has a dmarc field', () => { - const demoType = emailScanType.getFields() - - expect(demoType).toHaveProperty('dmarc') - expect(demoType.dmarc.type).toMatchObject(dmarcConnection.connectionType) - }) - it('has a spf field', () => { - const demoType = emailScanType.getFields() - - expect(demoType).toHaveProperty('spf') - expect(demoType.spf.type).toMatchObject(spfConnection.connectionType) - }) - }) - describe('testing field resolvers', () => { - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = emailScanType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { _key: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the dkim resolver', () => { - it('returns the resolved value', async () => { - const demoType = emailScanType.getFields() - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('dkim', 'dkim1'), - node: { - _id: 'dkimGuidanceTags/dkim1', - _key: 'dkim1', - _rev: 'rev', - _type: 'dkim', - id: 'dkim1', - domainId: 'domains/1', - timestamp: '2020-10-02T12:43:39Z', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('dkim', 'dkim1'), - endCursor: toGlobalId('dkim', 'dkim1'), - }, - } - - await expect( - demoType.dkim.resolve( - { _id: 'domains/1' }, - { first: 1 }, - { - loaders: { - loadDkimConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the dmarc resolver', () => { - it('returns the resolved value', async () => { - const demoType = emailScanType.getFields() - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('dmarc', 'dmarc1'), - node: { - _id: 'dmarcGuidanceTags/dmarc1', - _key: 'dmarc1', - _rev: 'rev', - _type: 'dmarc', - id: 'dmarc1', - domainId: 'domains/1', - dmarcPhase: 1, - timestamp: '2020-10-02T12:43:39Z', - pPolicy: 'pPolicy', - pct: 100, - record: 'txtRecord', - spPolicy: 'spPolicy', - guidanceTags: ['dmarc1'], - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('dmarc', 'dmarc1'), - endCursor: toGlobalId('dmarc', 'dmarc1'), - }, - } - - await expect( - demoType.dmarc.resolve( - { _id: 'domains/1' }, - { first: 1 }, - { - loaders: { - loadDmarcConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the spf resolver', () => { - it('returns the resolved value', async () => { - const demoType = emailScanType.getFields() - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('spf', 'spf1'), - node: { - _id: 'spfGuidanceTags/spf1', - _key: 'spf1', - _rev: 'rev', - _type: 'spf', - id: 'spf1', - domainId: 'domains/1', - lookups: 5, - record: 'txtRecord', - spfDefault: 'default', - timestamp: '2020-10-02T12:43:39Z', - guidanceTags: ['spf1'], - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('spf', 'spf1'), - endCursor: toGlobalId('spf', 'spf1'), - }, - } - await expect( - demoType.spf.resolve( - { _id: 'domains/1' }, - { first: 1 }, - { - loaders: { - loadSpfConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/spf-connection.test.js b/api-js/src/email-scan/objects/__tests__/spf-connection.test.js deleted file mode 100644 index 1caf88f637..0000000000 --- a/api-js/src/email-scan/objects/__tests__/spf-connection.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { spfConnection } from '../index' - -describe('given the spf connection object', () => { - describe('testing its field definitions', () => { - it('has a totalCount field', () => { - const demoType = spfConnection.connectionType.getFields() - - expect(demoType).toHaveProperty('totalCount') - expect(demoType.totalCount.type).toMatchObject(GraphQLInt) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the totalCount resolver', () => { - it('returns the resolved value', () => { - const demoType = spfConnection.connectionType.getFields() - - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/spf-sub.test.js b/api-js/src/email-scan/objects/__tests__/spf-sub.test.js deleted file mode 100644 index 7bcce28d1f..0000000000 --- a/api-js/src/email-scan/objects/__tests__/spf-sub.test.js +++ /dev/null @@ -1,403 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList, GraphQLInt, GraphQLID } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { databaseOptions } from '../../../../database-options' -import { loadSpfGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { guidanceTagType } from '../../../guidance-tag/objects' -import { spfSubType } from '../index' -import { domainType } from '../../../domain/objects' -import { StatusEnum } from '../../../enums' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the spfSubType object', () => { - describe('testing its field definitions', () => { - it('has sharedId field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('sharedId') - expect(demoType.sharedId.type).toMatchObject(GraphQLID) - }) - it('has a domain field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a status field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(StatusEnum) - }) - it('has lookups field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('lookups') - expect(demoType.lookups.type).toMatchObject(GraphQLInt) - }) - it('has record field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('record') - expect(demoType.record.type).toMatchObject(GraphQLString) - }) - it('has spfDefault field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('spfDefault') - expect(demoType.spfDefault.type).toMatchObject(GraphQLString) - }) - it('has a rawJson field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has negativeGuidanceTags field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has neutralGuidanceTags field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has positiveGuidanceTags field', () => { - const demoType = spfSubType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the sharedId resolver', () => { - it('returns the parsed value', () => { - const demoType = spfSubType.getFields() - - expect(demoType.sharedId.resolve({ sharedId: 'sharedId' })).toEqual( - 'sharedId', - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = spfSubType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainKey: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the status resolver', () => { - it('returns the parsed value', () => { - const demoType = spfSubType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the lookups resolver', () => { - it('returns the parsed value', () => { - const demoType = spfSubType.getFields() - - expect(demoType.lookups.resolve({ lookups: 10 })).toEqual(10) - }) - }) - describe('testing the record resolver', () => { - it('returns the parsed value', () => { - const demoType = spfSubType.getFields() - - expect(demoType.record.resolve({ record: 'txtRecord' })).toEqual( - 'txtRecord', - ) - }) - }) - describe('testing the spfDefault resolver', () => { - it('returns the parsed value', () => { - const demoType = spfSubType.getFields() - - expect( - demoType.spfDefault.resolve({ spfDefault: 'spfDefault' }), - ).toEqual('spfDefault') - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = spfSubType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - let query, drop, truncate, collections, spfGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - spfGT = await collections.spfGuidanceTags.save({ - _key: 'spf1', - en: { - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = spfSubType.getFields() - - const loader = loadSpfGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const negativeTags = ['spf1'] - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - {}, - { loaders: { loadSpfGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: spfGT._id, - _key: spfGT._key, - _rev: spfGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'spf1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - ]) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - let query, drop, truncate, collections, spfGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - spfGT = await collections.spfGuidanceTags.save({ - _key: 'spf1', - en: { - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = spfSubType.getFields() - - const loader = loadSpfGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const neutralTags = ['spf1'] - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - {}, - { loaders: { loadSpfGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: spfGT._id, - _key: spfGT._key, - _rev: spfGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'spf1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - ]) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - let query, drop, truncate, collections, spfGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - spfGT = await collections.spfGuidanceTags.save({ - _key: 'spf1', - en: { - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = spfSubType.getFields() - - const loader = loadSpfGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const positiveTags = ['spf1'] - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - {}, - { loaders: { loadSpfGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: spfGT._id, - _key: spfGT._key, - _rev: spfGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'spf1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - ]) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/__tests__/spf.test.js b/api-js/src/email-scan/objects/__tests__/spf.test.js deleted file mode 100644 index b430c70d93..0000000000 --- a/api-js/src/email-scan/objects/__tests__/spf.test.js +++ /dev/null @@ -1,390 +0,0 @@ -import { GraphQLNonNull, GraphQLID, GraphQLInt, GraphQLString } from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { GraphQLJSON, GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../../domain/objects' -import { guidanceTagConnection } from '../../../guidance-tag/objects' -import { spfType } from '../index' - -describe('given the spfType object', () => { - describe('testing the field definitions', () => { - it('has an id field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a domain field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a timestamp field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('timestamp') - expect(demoType.timestamp.type).toMatchObject(GraphQLDate) - }) - it('has a lookups field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('lookups') - expect(demoType.lookups.type).toMatchObject(GraphQLInt) - }) - it('has a record field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('record') - expect(demoType.record.type).toMatchObject(GraphQLString) - }) - it('has a spfDefault field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('spfDefault') - expect(demoType.spfDefault.type).toMatchObject(GraphQLString) - }) - it('has a rawJson field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has a negativeGuidanceTags field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a neutralGuidanceTags field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a positiveGuidanceTags field', () => { - const demoType = spfType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - }) - - describe('testing the field resolvers', () => { - describe('testing the id resolver', () => { - it('returns the resolved value', () => { - const demoType = spfType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('spf', '1')) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = spfType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainId: 'domains/1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the timestamp resolver', () => { - it('returns the resolved value', () => { - const demoType = spfType.getFields() - - expect( - demoType.timestamp.resolve({ timestamp: '2020-10-02T12:43:39Z' }), - ).toEqual(new Date('2020-10-02T12:43:39Z')) - }) - }) - describe('testing the lookups resolver', () => { - it('returns the resolved value', () => { - const demoType = spfType.getFields() - - expect(demoType.lookups.resolve({ lookups: 1 })).toEqual(1) - }) - }) - describe('testing the record resolver', () => { - it('returns the resolved value', () => { - const demoType = spfType.getFields() - - expect(demoType.record.resolve({ record: 'txtRecord' })).toEqual( - 'txtRecord', - ) - }) - }) - describe('testing the spfDefault resolver', () => { - it('returns the resolved value', () => { - const demoType = spfType.getFields() - - expect( - demoType.spfDefault.resolve({ spfDefault: 'spfDefault' }), - ).toEqual('spfDefault') - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = spfType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the guidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = spfType.getFields() - const guidanceTags = ['spf1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'spf1'), - node: { - _id: 'spfGuidanceTags/spf1', - _key: 'spf1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'spf1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'spf1'), - endCursor: toGlobalId('guidanceTags', 'spf1'), - }, - } - - await expect( - demoType.guidanceTags.resolve( - { guidanceTags }, - { first: 1 }, - { - loaders: { - loadSpfGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = spfType.getFields() - const negativeTags = ['spf1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'spf1'), - node: { - _id: 'spfGuidanceTags/spf1', - _key: 'spf1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'spf1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'spf1'), - endCursor: toGlobalId('guidanceTags', 'spf1'), - }, - } - - await expect( - demoType.negativeGuidanceTags.resolve( - { negativeTags }, - { first: 1 }, - { - loaders: { - loadSpfGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = spfType.getFields() - const neutralTags = ['spf1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'spf1'), - node: { - _id: 'spfGuidanceTags/spf1', - _key: 'spf1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'spf1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'spf1'), - endCursor: toGlobalId('guidanceTags', 'spf1'), - }, - } - - await expect( - demoType.neutralGuidanceTags.resolve( - { neutralTags }, - { first: 1 }, - { - loaders: { - loadSpfGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = spfType.getFields() - const positiveTags = ['spf1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'spf1'), - node: { - _id: 'spfGuidanceTags/spf1', - _key: 'spf1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'spf1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'spf1', - tagName: 'SPF-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'spf1'), - endCursor: toGlobalId('guidanceTags', 'spf1'), - }, - } - - await expect( - demoType.positiveGuidanceTags.resolve( - { positiveTags }, - { first: 1 }, - { - loaders: { - loadSpfGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/email-scan/objects/dkim-connection.js b/api-js/src/email-scan/objects/dkim-connection.js deleted file mode 100644 index dd2fb17e85..0000000000 --- a/api-js/src/email-scan/objects/dkim-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { dkimType } from './dkim' - -export const dkimConnection = connectionDefinitions({ - name: 'DKIM', - nodeType: dkimType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of dkim scans related to a given domain.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/dkim-result-connection.js b/api-js/src/email-scan/objects/dkim-result-connection.js deleted file mode 100644 index dd3d5ae442..0000000000 --- a/api-js/src/email-scan/objects/dkim-result-connection.js +++ /dev/null @@ -1,17 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { dkimResultType } from './dkim-result' - -export const dkimResultConnection = connectionDefinitions({ - name: 'DKIMResult', - nodeType: dkimResultType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: - 'The total amount of dkim results related to a given domain.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/dkim-result-sub.js b/api-js/src/email-scan/objects/dkim-result-sub.js deleted file mode 100644 index 1732dcdd88..0000000000 --- a/api-js/src/email-scan/objects/dkim-result-sub.js +++ /dev/null @@ -1,67 +0,0 @@ -import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { guidanceTagType } from '../../guidance-tag/objects' - -export const dkimResultSubType = new GraphQLObjectType({ - name: 'DkimResultSub', - description: 'Individual one-off scans results for the given dkim selector.', - fields: () => ({ - selector: { - type: GraphQLString, - description: 'The selector the scan was ran on.', - resolve: ({ selector }) => selector, - }, - record: { - type: GraphQLString, - description: 'DKIM record retrieved during the scan of the domain.', - resolve: ({ record }) => record, - }, - keyLength: { - type: GraphQLString, - description: 'Size of the Public Key in bits', - resolve: ({ keyLength }) => keyLength, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - negativeGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: 'Negative guidance tags found during scan.', - resolve: async ( - { negativeTags }, - _args, - { loaders: { loadDkimGuidanceTagByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagByTagId.loadMany(negativeTags) - return dkimTags - }, - }, - neutralGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: 'Neutral guidance tags found during scan.', - resolve: async ( - { neutralTags }, - _args, - { loaders: { loadDkimGuidanceTagByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagByTagId.loadMany(neutralTags) - return dkimTags - }, - }, - positiveGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: 'Positive guidance tags found during scan.', - resolve: async ( - { positiveTags }, - _args, - { loaders: { loadDkimGuidanceTagByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagByTagId.loadMany(positiveTags) - return dkimTags - }, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/dkim-result.js b/api-js/src/email-scan/objects/dkim-result.js deleted file mode 100644 index 6ffa55b070..0000000000 --- a/api-js/src/email-scan/objects/dkim-result.js +++ /dev/null @@ -1,137 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLJSON } from 'graphql-scalars' - -import { dkimType } from './dkim' -import { nodeInterface } from '../../node' -import { guidanceTagOrder } from '../../guidance-tag/inputs' -import { guidanceTagConnection } from '../../guidance-tag/objects' - -export const dkimResultType = new GraphQLObjectType({ - name: 'DKIMResult', - fields: () => ({ - id: globalIdField('dkimResult'), - dkim: { - type: dkimType, - description: 'The DKIM scan information that this result belongs to.', - resolve: async ({ dkimId }, _, { loaders: { loadDkimByKey } }) => { - const dkimKey = dkimId.split('/')[1] - const dkim = await loadDkimByKey.load(dkimKey) - dkim.id = dkim._key - return dkim - }, - }, - selector: { - type: GraphQLString, - description: 'The selector the scan was ran on.', - resolve: ({ selector }) => selector, - }, - record: { - type: GraphQLString, - description: 'DKIM record retrieved during the scan of the domain.', - resolve: ({ record }) => record, - }, - keyLength: { - type: GraphQLString, - description: 'Size of the Public Key in bits', - resolve: ({ keyLength }) => keyLength, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - guidanceTags: { - type: guidanceTagConnection.connectionType, - deprecationReason: - 'This has been sub-divided into neutral, negative, and positive tags.', - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: 'Guidance tags found during scan.', - resolve: async ( - { guidanceTags }, - args, - { loaders: { loadDkimGuidanceTagConnectionsByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagConnectionsByTagId({ - dkimGuidanceTags: guidanceTags, - ...args, - }) - return dkimTags - }, - }, - negativeGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: 'Negative guidance tags found during scan.', - resolve: async ( - { negativeTags }, - args, - { loaders: { loadDkimGuidanceTagConnectionsByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagConnectionsByTagId({ - dkimGuidanceTags: negativeTags, - ...args, - }) - return dkimTags - }, - }, - neutralGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: 'Neutral guidance tags found during scan.', - resolve: async ( - { neutralTags }, - args, - { loaders: { loadDkimGuidanceTagConnectionsByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagConnectionsByTagId({ - dkimGuidanceTags: neutralTags, - ...args, - }) - return dkimTags - }, - }, - positiveGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: 'Positive guidance tags found during scan.', - resolve: async ( - { positiveTags }, - args, - { loaders: { loadDkimGuidanceTagConnectionsByTagId } }, - ) => { - const dkimTags = await loadDkimGuidanceTagConnectionsByTagId({ - dkimGuidanceTags: positiveTags, - ...args, - }) - return dkimTags - }, - }, - }), - interfaces: [nodeInterface], - description: 'Individual scans results for the given DKIM selector.', -}) diff --git a/api-js/src/email-scan/objects/dkim-sub.js b/api-js/src/email-scan/objects/dkim-sub.js deleted file mode 100644 index b0d9b4783a..0000000000 --- a/api-js/src/email-scan/objects/dkim-sub.js +++ /dev/null @@ -1,36 +0,0 @@ -import { GraphQLObjectType, GraphQLList, GraphQLID } from 'graphql' - -import { domainType } from '../../domain/objects' -import { StatusEnum } from '../../enums' -import { dkimResultSubType } from './dkim-result-sub' - -export const dkimSubType = new GraphQLObjectType({ - name: 'DkimSub', - description: - 'DKIM gql object containing the fields for the `dkimScanData` subscription.', - fields: () => ({ - sharedId: { - type: GraphQLID, - description: `The shared id to match scans together.`, - resolve: ({ sharedId }) => sharedId, - }, - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainKey }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(domainKey) - return domain - }, - }, - status: { - type: StatusEnum, - description: 'The success status of the scan.', - resolve: ({ status }) => status, - }, - results: { - type: GraphQLList(dkimResultSubType), - description: 'Individual scans results for each dkim selector.', - resolve: ({ results }) => results, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/dkim.js b/api-js/src/email-scan/objects/dkim.js deleted file mode 100644 index 4b2421e0d9..0000000000 --- a/api-js/src/email-scan/objects/dkim.js +++ /dev/null @@ -1,58 +0,0 @@ -import { GraphQLObjectType } from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLDate } from 'graphql-scalars' - -import { dkimResultConnection } from './dkim-result-connection' -import { dkimResultOrder } from '../inputs' -import { domainType } from '../../domain/objects' -import { nodeInterface } from '../../node' - -export const dkimType = new GraphQLObjectType({ - name: 'DKIM', - fields: () => ({ - id: globalIdField('dkim'), - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainId }, _, { loaders: { loadDomainByKey } }) => { - const domainKey = domainId.split('/')[1] - const domain = await loadDomainByKey.load(domainKey) - domain.id = domain._key - return domain - }, - }, - timestamp: { - type: GraphQLDate, - description: `The time when the scan was initiated.`, - resolve: ({ timestamp }) => new Date(timestamp), - }, - results: { - type: dkimResultConnection.connectionType, - args: { - orderBy: { - type: dkimResultOrder, - description: 'Ordering options for DKIM result connections.', - }, - ...connectionArgs, - }, - description: 'Individual scans results for each DKIM selector.', - resolve: async ( - { _id }, - args, - { loaders: { loadDkimResultConnectionsByDkimId } }, - ) => { - const dkimResults = await loadDkimResultConnectionsByDkimId({ - dkimId: _id, - ...args, - }) - return dkimResults - }, - }, - }), - interfaces: [nodeInterface], - description: `DomainKeys Identified Mail (DKIM) permits a person, role, or -organization that owns the signing domain to claim some -responsibility for a message by associating the domain with the -message. This can be an author's organization, an operational relay, -or one of their agents.`, -}) diff --git a/api-js/src/email-scan/objects/dmarc-connection.js b/api-js/src/email-scan/objects/dmarc-connection.js deleted file mode 100644 index a76f3cb858..0000000000 --- a/api-js/src/email-scan/objects/dmarc-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { dmarcType } from './dmarc' - -export const dmarcConnection = connectionDefinitions({ - name: 'DMARC', - nodeType: dmarcType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of dmarc scans related to a given domain.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/dmarc-sub.js b/api-js/src/email-scan/objects/dmarc-sub.js deleted file mode 100644 index 4fd8e09427..0000000000 --- a/api-js/src/email-scan/objects/dmarc-sub.js +++ /dev/null @@ -1,112 +0,0 @@ -import { - GraphQLInt, - GraphQLObjectType, - GraphQLString, - GraphQLList, - GraphQLID, -} from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { guidanceTagType } from '../../guidance-tag/objects' -import { StatusEnum } from '../../enums' - -export const dmarcSubType = new GraphQLObjectType({ - name: 'DmarcSub', - description: - 'DMARC gql object containing the fields for the `dkimScanData` subscription.', - fields: () => ({ - sharedId: { - type: GraphQLID, - description: `The shared id to match scans together.`, - resolve: ({ sharedId }) => sharedId, - }, - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainKey }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(domainKey) - return domain - }, - }, - dmarcPhase: { - type: GraphQLString, - description: 'The current dmarc phase the domain is compliant to.', - resolve: ({ phase }) => phase, - }, - status: { - type: StatusEnum, - description: 'The success status of the scan.', - resolve: ({ status }) => status, - }, - record: { - type: GraphQLString, - description: `DMARC record retrieved during scan.`, - resolve: ({ record }) => record, - }, - pPolicy: { - type: GraphQLString, - description: `The requested policy you wish mailbox providers to apply -when your email fails DMARC authentication and alignment checks. `, - resolve: ({ pPolicy }) => pPolicy, - }, - spPolicy: { - type: GraphQLString, - description: `This tag is used to indicate a requested policy for all -subdomains where mail is failing the DMARC authentication and alignment checks.`, - resolve: ({ spPolicy }) => spPolicy, - }, - pct: { - type: GraphQLInt, - description: `The percentage of messages to which the DMARC policy is to be applied.`, - resolve: ({ pct }) => pct, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - negativeGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Negative guidance tags found during DMARC Scan.`, - resolve: async ( - { negativeTags }, - _args, - { loaders: { loadDmarcGuidanceTagByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagByTagId.loadMany( - negativeTags, - ) - return dmarcTags - }, - }, - neutralGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Neutral guidance tags found during DMARC Scan.`, - resolve: async ( - { neutralTags }, - _args, - { loaders: { loadDmarcGuidanceTagByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagByTagId.loadMany( - neutralTags, - ) - return dmarcTags - }, - }, - positiveGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Positive guidance tags found during DMARC Scan.`, - resolve: async ( - { positiveTags }, - _args, - { loaders: { loadDmarcGuidanceTagByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagByTagId.loadMany( - positiveTags, - ) - return dmarcTags - }, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/dmarc.js b/api-js/src/email-scan/objects/dmarc.js deleted file mode 100644 index 9703be47b9..0000000000 --- a/api-js/src/email-scan/objects/dmarc.js +++ /dev/null @@ -1,153 +0,0 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLJSON, GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { nodeInterface } from '../../node' -import { guidanceTagOrder } from '../../guidance-tag/inputs' -import { guidanceTagConnection } from '../../guidance-tag/objects' - -export const dmarcType = new GraphQLObjectType({ - name: 'DMARC', - fields: () => ({ - id: globalIdField('dmarc'), - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainId }, _, { loaders: { loadDomainByKey } }) => { - const domainKey = domainId.split('/')[1] - const domain = await loadDomainByKey.load(domainKey) - domain.id = domain._key - return domain - }, - }, - timestamp: { - type: GraphQLDate, - description: `The time when the scan was initiated.`, - resolve: ({ timestamp }) => new Date(timestamp), - }, - record: { - type: GraphQLString, - description: `DMARC record retrieved during scan.`, - resolve: ({ record }) => record, - }, - pPolicy: { - type: GraphQLString, - description: `The requested policy you wish mailbox providers to apply -when your email fails DMARC authentication and alignment checks. `, - resolve: ({ pPolicy }) => pPolicy, - }, - spPolicy: { - type: GraphQLString, - description: `This tag is used to indicate a requested policy for all -subdomains where mail is failing the DMARC authentication and alignment checks.`, - resolve: ({ spPolicy }) => spPolicy, - }, - pct: { - type: GraphQLInt, - description: `The percentage of messages to which the DMARC policy is to be applied.`, - resolve: ({ pct }) => pct, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - guidanceTags: { - type: guidanceTagConnection.connectionType, - deprecationReason: - 'This has been sub-divided into neutral, negative, and positive tags.', - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Guidance tags found during DMARC Scan.`, - resolve: async ( - { guidanceTags }, - args, - { loaders: { loadDmarcGuidanceTagConnectionsByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagConnectionsByTagId({ - dmarcGuidanceTags: guidanceTags, - ...args, - }) - return dmarcTags - }, - }, - negativeGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Negative guidance tags found during DMARC Scan.`, - resolve: async ( - { negativeTags }, - args, - { loaders: { loadDmarcGuidanceTagConnectionsByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagConnectionsByTagId({ - dmarcGuidanceTags: negativeTags, - ...args, - }) - return dmarcTags - }, - }, - neutralGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Neutral guidance tags found during DMARC Scan.`, - resolve: async ( - { neutralTags }, - args, - { loaders: { loadDmarcGuidanceTagConnectionsByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagConnectionsByTagId({ - dmarcGuidanceTags: neutralTags, - ...args, - }) - return dmarcTags - }, - }, - positiveGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Positive guidance tags found during DMARC Scan.`, - resolve: async ( - { positiveTags }, - args, - { loaders: { loadDmarcGuidanceTagConnectionsByTagId } }, - ) => { - const dmarcTags = await loadDmarcGuidanceTagConnectionsByTagId({ - dmarcGuidanceTags: positiveTags, - ...args, - }) - return dmarcTags - }, - }, - }), - interfaces: [nodeInterface], - description: `Domain-based Message Authentication, Reporting, and Conformance -(DMARC) is a scalable mechanism by which a mail-originating -organization can express domain-level policies and preferences for -message validation, disposition, and reporting, that a mail-receiving -organization can use to improve mail handling.`, -}) diff --git a/api-js/src/email-scan/objects/email-scan.js b/api-js/src/email-scan/objects/email-scan.js deleted file mode 100644 index 4111fa10a9..0000000000 --- a/api-js/src/email-scan/objects/email-scan.js +++ /dev/null @@ -1,115 +0,0 @@ -import { GraphQLObjectType } from 'graphql' -import { connectionArgs } from 'graphql-relay' -import { GraphQLDate } from 'graphql-scalars' - -import { dkimOrder, dmarcOrder, spfOrder } from '../inputs' -import { dkimConnection } from './dkim-connection' -import { dmarcConnection } from './dmarc-connection' -import { spfConnection } from './spf-connection' -import { domainType } from '../../domain/objects' - -export const emailScanType = new GraphQLObjectType({ - name: 'EmailScan', - fields: () => ({ - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ _key }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(_key) - domain.id = domain._key - return domain - }, - }, - dkim: { - type: dkimConnection.connectionType, - args: { - startDate: { - type: GraphQLDate, - description: 'Start date for date filter.', - }, - endDate: { - type: GraphQLDate, - description: 'End date for date filter.', - }, - orderBy: { - type: dkimOrder, - description: 'Ordering options for dkim connections.', - }, - ...connectionArgs, - }, - description: `DomainKeys Identified Mail (DKIM) Signatures scan results.`, - resolve: async ( - { _id }, - args, - { loaders: { loadDkimConnectionsByDomainId } }, - ) => { - const dkim = await loadDkimConnectionsByDomainId({ - domainId: _id, - ...args, - }) - return dkim - }, - }, - dmarc: { - type: dmarcConnection.connectionType, - args: { - startDate: { - type: GraphQLDate, - description: 'Start date for date filter.', - }, - endDate: { - type: GraphQLDate, - description: 'End date for date filter.', - }, - orderBy: { - type: dmarcOrder, - description: 'Ordering options for dmarc connections.', - }, - ...connectionArgs, - }, - description: `Domain-based Message Authentication, Reporting, and Conformance (DMARC) scan results.`, - resolve: async ( - { _id }, - args, - { loaders: { loadDmarcConnectionsByDomainId } }, - ) => { - const dmarc = await loadDmarcConnectionsByDomainId({ - domainId: _id, - ...args, - }) - return dmarc - }, - }, - spf: { - type: spfConnection.connectionType, - args: { - startDate: { - type: GraphQLDate, - description: 'Start date for date filter.', - }, - endDate: { - type: GraphQLDate, - description: 'End date for date filter.', - }, - orderBy: { - type: spfOrder, - description: 'Ordering options for spf connections.', - }, - ...connectionArgs, - }, - description: `Sender Policy Framework (SPF) scan results.`, - resolve: async ( - { _id }, - args, - { loaders: { loadSpfConnectionsByDomainId } }, - ) => { - const spf = await loadSpfConnectionsByDomainId({ - domainId: _id, - ...args, - }) - return spf - }, - }, - }), - description: `Results of DKIM, DMARC, and SPF scans on the given domain.`, -}) diff --git a/api-js/src/email-scan/objects/index.js b/api-js/src/email-scan/objects/index.js deleted file mode 100644 index dc9d20afb9..0000000000 --- a/api-js/src/email-scan/objects/index.js +++ /dev/null @@ -1,13 +0,0 @@ -export * from './dkim' -export * from './dkim-sub' -export * from './dkim-connection' -export * from './dkim-result' -export * from './dkim-result-sub' -export * from './dkim-result-connection' -export * from './dmarc' -export * from './dmarc-sub' -export * from './dmarc-connection' -export * from './email-scan' -export * from './spf' -export * from './spf-sub' -export * from './spf-connection' diff --git a/api-js/src/email-scan/objects/spf-connection.js b/api-js/src/email-scan/objects/spf-connection.js deleted file mode 100644 index 90e45a735b..0000000000 --- a/api-js/src/email-scan/objects/spf-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { spfType } from './spf' - -export const spfConnection = connectionDefinitions({ - name: 'SPF', - nodeType: spfType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of spf scans related to a given domain.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/spf-sub.js b/api-js/src/email-scan/objects/spf-sub.js deleted file mode 100644 index 99773d43d9..0000000000 --- a/api-js/src/email-scan/objects/spf-sub.js +++ /dev/null @@ -1,94 +0,0 @@ -import { - GraphQLInt, - GraphQLObjectType, - GraphQLString, - GraphQLList, - GraphQLID, -} from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { guidanceTagType } from '../../guidance-tag/objects' -import { StatusEnum } from '../../enums' - -export const spfSubType = new GraphQLObjectType({ - name: 'SpfSub', - description: - 'SPF gql object containing the fields for the `dkimScanData` subscription.', - fields: () => ({ - sharedId: { - type: GraphQLID, - description: `The shared id to match scans together.`, - resolve: ({ sharedId }) => sharedId, - }, - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainKey }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(domainKey) - return domain - }, - }, - status: { - type: StatusEnum, - description: 'The success status of the scan.', - resolve: ({ status }) => status, - }, - lookups: { - type: GraphQLInt, - description: `The amount of DNS lookups.`, - resolve: ({ lookups }) => lookups, - }, - record: { - type: GraphQLString, - description: `SPF record retrieved during the scan of the given domain.`, - resolve: ({ record }) => record, - }, - spfDefault: { - type: GraphQLString, - description: `Instruction of what a recipient should do if there is not a match to your SPF record.`, - resolve: ({ spfDefault }) => spfDefault, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - negativeGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Negative guidance tags found during scan.`, - resolve: async ( - { negativeTags }, - _args, - { loaders: { loadSpfGuidanceTagByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagByTagId.loadMany(negativeTags) - return spfTags - }, - }, - neutralGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Neutral guidance tags found during scan.`, - resolve: async ( - { neutralTags }, - _args, - { loaders: { loadSpfGuidanceTagByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagByTagId.loadMany(neutralTags) - return spfTags - }, - }, - positiveGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Positive guidance tags found during scan.`, - resolve: async ( - { positiveTags }, - _args, - { loaders: { loadSpfGuidanceTagByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagByTagId.loadMany(positiveTags) - return spfTags - }, - }, - }), -}) diff --git a/api-js/src/email-scan/objects/spf.js b/api-js/src/email-scan/objects/spf.js deleted file mode 100644 index b0b5e71276..0000000000 --- a/api-js/src/email-scan/objects/spf.js +++ /dev/null @@ -1,148 +0,0 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLJSON, GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { nodeInterface } from '../../node' -import { guidanceTagOrder } from '../../guidance-tag/inputs' -import { guidanceTagConnection } from '../../guidance-tag/objects' - -export const spfType = new GraphQLObjectType({ - name: 'SPF', - fields: () => ({ - id: globalIdField('spf'), - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainId }, _, { loaders: { loadDomainByKey } }) => { - const domainKey = domainId.split('/')[1] - const domain = await loadDomainByKey.load(domainKey) - domain.id = domain._key - return domain - }, - }, - timestamp: { - type: GraphQLDate, - description: `The time the scan was initiated.`, - resolve: ({ timestamp }) => new Date(timestamp), - }, - lookups: { - type: GraphQLInt, - description: `The amount of DNS lookups.`, - resolve: ({ lookups }) => lookups, - }, - record: { - type: GraphQLString, - description: `SPF record retrieved during the scan of the given domain.`, - resolve: ({ record }) => record, - }, - spfDefault: { - type: GraphQLString, - description: `Instruction of what a recipient should do if there is not a match to your SPF record.`, - resolve: ({ spfDefault }) => spfDefault, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - guidanceTags: { - type: guidanceTagConnection.connectionType, - deprecationReason: - 'This has been sub-divided into neutral, negative, and positive tags.', - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Guidance tags found during scan.`, - resolve: async ( - { guidanceTags }, - args, - { loaders: { loadSpfGuidanceTagConnectionsByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagConnectionsByTagId({ - spfGuidanceTags: guidanceTags, - ...args, - }) - return spfTags - }, - }, - negativeGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Negative guidance tags found during scan.`, - resolve: async ( - { negativeTags }, - args, - { loaders: { loadSpfGuidanceTagConnectionsByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagConnectionsByTagId({ - spfGuidanceTags: negativeTags, - ...args, - }) - return spfTags - }, - }, - neutralGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Neutral guidance tags found during scan.`, - resolve: async ( - { neutralTags }, - args, - { loaders: { loadSpfGuidanceTagConnectionsByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagConnectionsByTagId({ - spfGuidanceTags: neutralTags, - ...args, - }) - return spfTags - }, - }, - positiveGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Positive guidance tags found during scan.`, - resolve: async ( - { positiveTags }, - args, - { loaders: { loadSpfGuidanceTagConnectionsByTagId } }, - ) => { - const spfTags = await loadSpfGuidanceTagConnectionsByTagId({ - spfGuidanceTags: positiveTags, - ...args, - }) - return spfTags - }, - }, - }), - interfaces: [nodeInterface], - description: `Email on the Internet can be forged in a number of ways. In -particular, existing protocols place no restriction on what a sending -host can use as the "MAIL FROM" of a message or the domain given on -the SMTP HELO/EHLO commands. Version 1 of the Sender Policy Framework (SPF) -protocol is where Administrative Management Domains (ADMDs) can explicitly -authorize the hosts that are allowed to use their domain names, and a -receiving host can check such authorization.`, -}) diff --git a/api-js/src/email-scan/subscriptions/__tests__/dkim-scan-data.test.js b/api-js/src/email-scan/subscriptions/__tests__/dkim-scan-data.test.js deleted file mode 100644 index 6d27b23d3d..0000000000 --- a/api-js/src/email-scan/subscriptions/__tests__/dkim-scan-data.test.js +++ /dev/null @@ -1,345 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import Redis from 'ioredis' -import { - graphql, - GraphQLSchema, - subscribe, - parse, - GraphQLObjectType, - GraphQLInt, - GraphQLID, -} from 'graphql' -import { RedisPubSub } from 'graphql-redis-subscriptions' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadDkimGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' - -const { - REDIS_PORT_NUMBER, - REDIS_DOMAIN_NAME, - DKIM_SCAN_CHANNEL, - DB_PASS: rootPass, - DB_URL: url, -} = process.env - -describe('given the dkimScanData subscription', () => { - let pubsub, - schema, - publisherClient, - subscriberClient, - query, - truncate, - collections, - drop, - options, - dkimScan, - createSubscriptionMutation, - redis, - pub, - domain, - sharedId, - status - - beforeAll(async () => { - options = { - host: REDIS_DOMAIN_NAME, - port: REDIS_PORT_NUMBER, - } - - dkimScan = { - results: [ - { - selector: 'selector', - record: 'record', - keyLength: 'keyLength', - rawJson: { - missing: true, - }, - negativeTags: ['dkim1'], - neutralTags: ['dkim1'], - positiveTags: ['dkim1'], - }, - ], - } - - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - - publisherClient = new Redis(options) - subscriberClient = new Redis(options) - redis = new Redis(options) - pub = new Redis(options) - - pubsub = new RedisPubSub({ - publisher: publisherClient, - subscriber: subscriberClient, - }) - await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - en: { - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - sharedId = 'some-shared-id' - status = 'pass' - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await publisherClient.quit() - await subscriberClient.quit() - await redis.quit() - await pub.quit() - await drop() - }) - - it('returns the subscription data', async () => { - createSubscriptionMutation = () => - new GraphQLObjectType({ - name: 'Mutation', - fields: () => ({ - testMutation: { - type: GraphQLInt, - args: { - subscriptionId: { - type: GraphQLID, - }, - }, - resolve: async (_source, { subscriptionId }) => { - await redis.subscribe( - `${DKIM_SCAN_CHANNEL}/${subscriptionId}`, - (_err, _count) => { - pub.publish( - `${DKIM_SCAN_CHANNEL}/${subscriptionId}`, - JSON.stringify({ - sharedId: sharedId, - domainKey: domain._key, - status: status, - results: dkimScan, - }), - ) - }, - ) - return 1 - }, - }, - }), - }) - - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createSubscriptionMutation(), - subscription: createSubscriptionSchema(), - }) - - const triggerSubscription = setTimeout(() => { - graphql( - schema, - ` - mutation { - testMutation(subscriptionId: "uuid-1234") - } - `, - null, - { - Redis, - options, - }, - ) - }, 100) - - const data = await subscribe( - schema, - parse(` - subscription { - dkimScanData { - sharedId - domain { - domain - } - status - results { - selector - record - keyLength - rawJson - negativeGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - neutralGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - positiveGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - } - } - } - `), - triggerSubscription, - { - pubsubs: { - dkimPubSub: pubsub, - }, - userKey: 'uuid-1234', - loaders: { - loadDomainByKey: loadDomainByKey({ query, userKey: '1', i18n: {} }), - loadDkimGuidanceTagByTagId: loadDkimGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }), - }, - }, - {}, - ) - - const result = await data.next() - - const expectedResult = { - data: { - dkimScanData: { - sharedId: sharedId, - domain: { - domain: 'test.domain.gc.ca', - }, - status: status.toUpperCase(), - results: [ - { - selector: 'selector', - record: 'record', - keyLength: 'keyLength', - rawJson: '{"missing":true}', - negativeGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'dkim1'), - tagId: 'dkim1', - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - neutralGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'dkim1'), - tagId: 'dkim1', - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - positiveGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'dkim1'), - tagId: 'dkim1', - tagName: 'DKIM-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - }, - ], - }, - }, - } - - expect(result.value).toEqual(expectedResult) - }) -}) diff --git a/api-js/src/email-scan/subscriptions/__tests__/dmarc-scan-data.test.js b/api-js/src/email-scan/subscriptions/__tests__/dmarc-scan-data.test.js deleted file mode 100644 index 4f898f3b10..0000000000 --- a/api-js/src/email-scan/subscriptions/__tests__/dmarc-scan-data.test.js +++ /dev/null @@ -1,342 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import Redis from 'ioredis' -import { - graphql, - GraphQLSchema, - subscribe, - parse, - GraphQLObjectType, - GraphQLInt, - GraphQLID, -} from 'graphql' -import { RedisPubSub } from 'graphql-redis-subscriptions' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadDmarcGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' - -const { - REDIS_PORT_NUMBER, - REDIS_DOMAIN_NAME, - DMARC_SCAN_CHANNEL, - DB_PASS: rootPass, - DB_URL: url, -} = process.env - -describe('given the dmarcScanData subscription', () => { - let pubsub, - schema, - publisherClient, - subscriberClient, - query, - truncate, - collections, - drop, - options, - dmarcScan, - createSubscriptionMutation, - redis, - pub, - domain, - sharedId, - status - - beforeAll(async () => { - options = { - host: REDIS_DOMAIN_NAME, - port: REDIS_PORT_NUMBER, - } - - dmarcScan = { - phase: 'deploy', - record: 'record', - pPolicy: 'pPolicy', - spPolicy: 'spPolicy', - pct: 100, - rawJson: { - missing: true, - }, - negativeTags: ['dmarc1'], - neutralTags: ['dmarc1'], - positiveTags: ['dmarc1'], - } - - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - - publisherClient = new Redis(options) - subscriberClient = new Redis(options) - redis = new Redis(options) - pub = new Redis(options) - - pubsub = new RedisPubSub({ - publisher: publisherClient, - subscriber: subscriberClient, - }) - - await collections.dmarcGuidanceTags.save({ - _key: 'dmarc1', - en: { - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - sharedId = 'some-shared-id' - status = 'pass' - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await publisherClient.quit() - await subscriberClient.quit() - await redis.quit() - await pub.quit() - await drop() - }) - - it('returns the subscription data', async () => { - createSubscriptionMutation = () => - new GraphQLObjectType({ - name: 'Mutation', - fields: () => ({ - testMutation: { - type: GraphQLInt, - args: { - subscriptionId: { - type: GraphQLID, - }, - }, - resolve: async (_source, { subscriptionId }) => { - await redis.subscribe( - `${DMARC_SCAN_CHANNEL}/${subscriptionId}`, - (_err, _count) => { - pub.publish( - `${DMARC_SCAN_CHANNEL}/${subscriptionId}`, - JSON.stringify({ - sharedId: sharedId, - domainKey: domain._key, - status: status, - results: dmarcScan, - }), - ) - }, - ) - return 1 - }, - }, - }), - }) - - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createSubscriptionMutation(), - subscription: createSubscriptionSchema(), - }) - - const triggerSubscription = setTimeout(() => { - graphql( - schema, - ` - mutation { - testMutation(subscriptionId: "uuid-1234") - } - `, - null, - { - Redis, - options, - }, - ) - }, 100) - - const data = await subscribe( - schema, - parse(` - subscription { - dmarcScanData { - sharedId - domain { - domain - } - status - dmarcPhase - record - pPolicy - spPolicy - pct - rawJson - negativeGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - neutralGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - positiveGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - } - } - `), - triggerSubscription, - { - pubsubs: { - dmarcPubSub: pubsub, - }, - userKey: 'uuid-1234', - loaders: { - loadDomainByKey: loadDomainByKey({ query, userKey: '1', i18n: {} }), - loadDmarcGuidanceTagByTagId: loadDmarcGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }), - }, - }, - {}, - ) - - const result = await data.next() - - const expectedResult = { - data: { - dmarcScanData: { - sharedId: sharedId, - domain: { - domain: 'test.domain.gc.ca', - }, - status: status.toUpperCase(), - dmarcPhase: 'deploy', - record: 'record', - pPolicy: 'pPolicy', - spPolicy: 'spPolicy', - pct: 100, - rawJson: '{"missing":true}', - negativeGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'dmarc1'), - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - neutralGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'dmarc1'), - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - positiveGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'dmarc1'), - tagId: 'dmarc1', - tagName: 'DMARC-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - }, - }, - } - - expect(result.value).toEqual(expectedResult) - }) -}) diff --git a/api-js/src/email-scan/subscriptions/__tests__/spf-scan-data.test.js b/api-js/src/email-scan/subscriptions/__tests__/spf-scan-data.test.js deleted file mode 100644 index 655713de1a..0000000000 --- a/api-js/src/email-scan/subscriptions/__tests__/spf-scan-data.test.js +++ /dev/null @@ -1,336 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import Redis from 'ioredis' -import { - graphql, - GraphQLSchema, - subscribe, - parse, - GraphQLObjectType, - GraphQLInt, - GraphQLID, -} from 'graphql' -import { RedisPubSub } from 'graphql-redis-subscriptions' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadSpfGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' - -const { - REDIS_PORT_NUMBER, - REDIS_DOMAIN_NAME, - SPF_SCAN_CHANNEL, - DB_PASS: rootPass, - DB_URL: url, -} = process.env - -describe('given the spfScanData subscription', () => { - let pubsub, - schema, - publisherClient, - subscriberClient, - query, - truncate, - collections, - drop, - options, - spfScan, - createSubscriptionMutation, - redis, - pub, - domain, - sharedId, - status - - beforeAll(async () => { - options = { - host: REDIS_DOMAIN_NAME, - port: REDIS_PORT_NUMBER, - } - - spfScan = { - lookups: 1, - record: 'record', - spfDefault: 'spfDefault', - rawJson: { - missing: true, - }, - negativeTags: ['spf1'], - neutralTags: ['spf1'], - positiveTags: ['spf1'], - } - - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - - publisherClient = new Redis(options) - subscriberClient = new Redis(options) - redis = new Redis(options) - pub = new Redis(options) - - pubsub = new RedisPubSub({ - publisher: publisherClient, - subscriber: subscriberClient, - }) - - await collections.spfGuidanceTags.save({ - _key: 'spf1', - en: { - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - sharedId = 'some-shared-id' - status = 'pass' - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await publisherClient.quit() - await subscriberClient.quit() - await redis.quit() - await pub.quit() - await drop() - }) - - it('returns the subscription data', async () => { - createSubscriptionMutation = () => - new GraphQLObjectType({ - name: 'Mutation', - fields: () => ({ - testMutation: { - type: GraphQLInt, - args: { - subscriptionId: { - type: GraphQLID, - }, - }, - resolve: async (_source, { subscriptionId }) => { - await redis.subscribe( - `${SPF_SCAN_CHANNEL}/${subscriptionId}`, - (_err, _count) => { - pub.publish( - `${SPF_SCAN_CHANNEL}/${subscriptionId}`, - JSON.stringify({ - sharedId: sharedId, - domainKey: domain._key, - status: status, - results: spfScan, - }), - ) - }, - ) - return 1 - }, - }, - }), - }) - - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createSubscriptionMutation(), - subscription: createSubscriptionSchema(), - }) - - const triggerSubscription = setTimeout(() => { - graphql( - schema, - ` - mutation { - testMutation(subscriptionId: "uuid-1234") - } - `, - null, - { - Redis, - options, - }, - ) - }, 100) - - const data = await subscribe( - schema, - parse(` - subscription { - spfScanData { - sharedId - domain { - domain - } - status - lookups - record - spfDefault - rawJson - negativeGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - neutralGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - positiveGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - } - } - `), - triggerSubscription, - { - pubsubs: { - spfPubSub: pubsub, - }, - userKey: 'uuid-1234', - loaders: { - loadDomainByKey: loadDomainByKey({ query, userKey: '1', i18n: {} }), - loadSpfGuidanceTagByTagId: loadSpfGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }), - }, - }, - {}, - ) - - const result = await data.next() - - const expectedResult = { - data: { - spfScanData: { - sharedId: sharedId, - domain: { - domain: 'test.domain.gc.ca', - }, - status: status.toUpperCase(), - lookups: 1, - record: 'record', - spfDefault: 'spfDefault', - rawJson: '{"missing":true}', - negativeGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'spf1'), - tagId: 'spf1', - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - neutralGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'spf1'), - tagId: 'spf1', - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - positiveGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'spf1'), - tagId: 'spf1', - tagName: 'SPF-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - }, - }, - } - - expect(result.value).toEqual(expectedResult) - }) -}) diff --git a/api-js/src/email-scan/subscriptions/dkim-scan-data.js b/api-js/src/email-scan/subscriptions/dkim-scan-data.js deleted file mode 100644 index dd0856293a..0000000000 --- a/api-js/src/email-scan/subscriptions/dkim-scan-data.js +++ /dev/null @@ -1,17 +0,0 @@ -import { dkimSubType } from '../objects' - -const { DKIM_SCAN_CHANNEL } = process.env - -export const dkimScanData = { - type: dkimSubType, - description: - 'This subscription allows the user to receive dkim data directly from the scanners in real time.', - resolve: ({ sharedId, domainKey, results, status }) => ({ - sharedId, - domainKey, - status, - ...results, - }), - subscribe: async (_context, _args, { pubsubs: { dkimPubSub }, userKey }) => - dkimPubSub.asyncIterator(`${DKIM_SCAN_CHANNEL}/${userKey}`), -} diff --git a/api-js/src/email-scan/subscriptions/dmarc-scan-data.js b/api-js/src/email-scan/subscriptions/dmarc-scan-data.js deleted file mode 100644 index f72fb0c729..0000000000 --- a/api-js/src/email-scan/subscriptions/dmarc-scan-data.js +++ /dev/null @@ -1,17 +0,0 @@ -import { dmarcSubType } from '../objects' - -const { DMARC_SCAN_CHANNEL } = process.env - -export const dmarcScanData = { - type: dmarcSubType, - description: - 'This subscription allows the user to receive dmarc data directly from the scanners in real time.', - resolve: ({ sharedId, domainKey, results, status }) => ({ - sharedId, - domainKey, - status, - ...results, - }), - subscribe: async (_context, _args, { pubsubs: { dmarcPubSub }, userKey }) => - dmarcPubSub.asyncIterator(`${DMARC_SCAN_CHANNEL}/${userKey}`), -} diff --git a/api-js/src/email-scan/subscriptions/index.js b/api-js/src/email-scan/subscriptions/index.js deleted file mode 100644 index 4e674b74c5..0000000000 --- a/api-js/src/email-scan/subscriptions/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './dkim-scan-data' -export * from './dmarc-scan-data' -export * from './spf-scan-data' diff --git a/api-js/src/email-scan/subscriptions/spf-scan-data.js b/api-js/src/email-scan/subscriptions/spf-scan-data.js deleted file mode 100644 index d35391f7df..0000000000 --- a/api-js/src/email-scan/subscriptions/spf-scan-data.js +++ /dev/null @@ -1,17 +0,0 @@ -import { spfSubType } from '../objects' - -const { SPF_SCAN_CHANNEL } = process.env - -export const spfScanData = { - type: spfSubType, - description: - 'This subscription allows the user to receive spf data directly from the scanners in real time.', - resolve: ({ sharedId, domainKey, results, status }) => ({ - sharedId, - domainKey, - status, - ...results, - }), - subscribe: async (_context, _args, { pubsubs: { spfPubSub }, userKey }) => - spfPubSub.asyncIterator(`${SPF_SCAN_CHANNEL}/${userKey}`), -} diff --git a/api-js/src/enums/affiliation-user-order-field.js b/api-js/src/enums/affiliation-user-order-field.js deleted file mode 100644 index 75d9967e87..0000000000 --- a/api-js/src/enums/affiliation-user-order-field.js +++ /dev/null @@ -1,12 +0,0 @@ -import { GraphQLEnumType } from 'graphql' - -export const AffiliationUserOrderField = new GraphQLEnumType({ - name: 'AffiliationUserOrderField', - description: 'Properties by which affiliation connections can be ordered.', - values: { - USER_USERNAME: { - value: 'user-username', - description: 'Order affiliation edges by username.', - }, - }, -}) diff --git a/api-js/src/enums/domain-order-field.js b/api-js/src/enums/domain-order-field.js deleted file mode 100644 index a4e8afe2a3..0000000000 --- a/api-js/src/enums/domain-order-field.js +++ /dev/null @@ -1,36 +0,0 @@ -import { GraphQLEnumType } from 'graphql' - -export const DomainOrderField = new GraphQLEnumType({ - name: 'DomainOrderField', - description: 'Properties by which domain connections can be ordered.', - values: { - DOMAIN: { - value: 'domain', - description: 'Order domains by domain.', - }, - LAST_RAN: { - value: 'last-ran', - description: 'Order domains by last ran.', - }, - DKIM_STATUS: { - value: 'dkim-status', - description: 'Order domains by dkim status.', - }, - DMARC_STATUS: { - value: 'dmarc-status', - description: 'Order domains by dmarc status.', - }, - HTTPS_STATUS: { - value: 'https-status', - description: 'Order domains by https status.', - }, - SPF_STATUS: { - value: 'spf-status', - description: 'Order domains by spf status.', - }, - SSL_STATUS: { - value: 'ssl-status', - description: 'Order domains by ssl status.', - }, - }, -}) diff --git a/api-js/src/enums/index.js b/api-js/src/enums/index.js deleted file mode 100644 index 5170a1df5b..0000000000 --- a/api-js/src/enums/index.js +++ /dev/null @@ -1,21 +0,0 @@ -export * from './affiliation-org-order-field' -export * from './affiliation-user-order-field' -export * from './dkim-order-field' -export * from './dkim-result-order-field' -export * from './dmarc-order-field' -export * from './dmarc-summary-order-field' -export * from './domain-order-field' -export * from './guidance-tag-order-field' -export * from './https-order-field' -export * from './languages' -export * from './order-direction' -export * from './organization-order-field' -export * from './period' -export * from './roles' -export * from './scan-types' -export * from './spf-order-field' -export * from './ssl-order-field' -export * from './status' -export * from './tfa-send-method' -export * from './verified-domain-order-field' -export * from './verified-organization-order-field' diff --git a/api-js/src/enums/roles.js b/api-js/src/enums/roles.js deleted file mode 100644 index a5cb871876..0000000000 --- a/api-js/src/enums/roles.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLEnumType } from 'graphql' - -export const RoleEnums = new GraphQLEnumType({ - name: 'RoleEnums', - values: { - USER: { - value: 'user', - description: 'A user who has been given access to view an organization.', - }, - ADMIN: { - value: 'admin', - description: - 'A user who has the same access as a user write account, but can define new user read/write accounts.', - }, - SUPER_ADMIN: { - value: 'super_admin', - description: - 'A user who has the same access as an admin, but can define new admins.', - }, - }, - description: 'An enum used to assign, and test users roles.', -}) diff --git a/api-js/src/env.js b/api-js/src/env.js deleted file mode 100644 index 2843dc84aa..0000000000 --- a/api-js/src/env.js +++ /dev/null @@ -1,2 +0,0 @@ -import dotenvSafe from 'dotenv-safe' -dotenvSafe.config() diff --git a/api-js/src/guidance-tag/index.js b/api-js/src/guidance-tag/index.js deleted file mode 100644 index 6d2a5b5879..0000000000 --- a/api-js/src/guidance-tag/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './inputs' -export * from './loaders' -export * from './objects' diff --git a/api-js/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js b/api-js/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js deleted file mode 100644 index c67d01c637..0000000000 --- a/api-js/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { guidanceTagOrder } from '../guidance-tag-order' -import { OrderDirection, GuidanceTagOrderField } from '../../../enums' - -describe('given the guidanceTagOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = guidanceTagOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = guidanceTagOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(GuidanceTagOrderField), - ) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags-connections.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags-connections.test.js deleted file mode 100644 index 2b8df6f8bf..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags-connections.test.js +++ /dev/null @@ -1,2241 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadAggregateGuidanceTagByTagId, - loadAggregateGuidanceTagConnectionsByTagId, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadAggregateGuidanceTagConnectionsByTagId loader', () => { - let query, drop, truncate, collections, user, i18n - - const consoleOutput = [] - const mockedConsole = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.error = mockedConsole - console.warn = mockedConsole - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - - await collections.aggregateGuidanceTags.save({ - _key: 'agg1', - en: { - tagName: 'a', - guidance: 'a', - }, - fr: { - tagName: 'a', - guidance: 'a', - }, - }) - await collections.aggregateGuidanceTags.save({ - _key: 'agg2', - en: { - tagName: 'b', - guidance: 'b', - }, - fr: { - tagName: 'b', - guidance: 'b', - }, - }) - await collections.aggregateGuidanceTags.save({ - _key: 'agg3', - en: { - tagName: 'c', - guidance: 'c', - }, - fr: { - tagName: 'c', - guidance: 'c', - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedAggregateTags[0]._key), - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedAggregateTags[1]._key), - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - node: { - ...expectedAggregateTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using the first limit', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - node: { - ...expectedAggregateTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using the last limit', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using the orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2', 'agg3'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg1'), - before: toGlobalId('guidanceTag', 'agg3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('order is set to DESC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2', 'agg3'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg3'), - before: toGlobalId('guidanceTag', 'agg1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2', 'agg3'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg1'), - before: toGlobalId('guidanceTag', 'agg3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('order is set to DESC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2', 'agg3'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg3'), - before: toGlobalId('guidanceTag', 'agg1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2', 'agg3'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg1'), - before: toGlobalId('guidanceTag', 'agg3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('order is set to DESC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2', 'agg3'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg3'), - before: toGlobalId('guidanceTag', 'agg1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no tags are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: 5, - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedAggregateTags[0]._key), - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedAggregateTags[1]._key), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId('guidanceTag', expectedAggregateTags[1]._key), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedAggregateTags[1]._key), - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedAggregateTags[0]._key), - node: { - ...expectedAggregateTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - endCursor: toGlobalId('guidanceTag', expectedAggregateTags[0]._key), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using the first limit', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedAggregateTags[0]._key), - node: { - ...expectedAggregateTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[0]._key, - ), - endCursor: toGlobalId('guidanceTag', expectedAggregateTags[0]._key), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using the last limit', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedAggregateTags[1]._key), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId('guidanceTag', expectedAggregateTags[1]._key), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('using the orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }, - ) - - const aggregateGuidanceTags = [ - 'agg1', - 'agg2', - 'agg3', - ] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg1'), - before: toGlobalId('guidanceTag', 'agg3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('order is set to DESC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }, - ) - - const aggregateGuidanceTags = [ - 'agg1', - 'agg2', - 'agg3', - ] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg3'), - before: toGlobalId('guidanceTag', 'agg1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }, - ) - - const aggregateGuidanceTags = [ - 'agg1', - 'agg2', - 'agg3', - ] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg1'), - before: toGlobalId('guidanceTag', 'agg3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('order is set to DESC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }, - ) - - const aggregateGuidanceTags = [ - 'agg1', - 'agg2', - 'agg3', - ] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg3'), - before: toGlobalId('guidanceTag', 'agg1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }, - ) - - const aggregateGuidanceTags = [ - 'agg1', - 'agg2', - 'agg3', - ] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg1'), - before: toGlobalId('guidanceTag', 'agg3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - describe('order is set to DESC', () => { - it('returns the guidance tags', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }, - ) - - const aggregateGuidanceTags = [ - 'agg1', - 'agg2', - 'agg3', - ] - - const aggregateTagLoader = loadAggregateGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedAggregateTags = await aggregateTagLoader.loadMany( - aggregateGuidanceTags, - ) - - const connectionArgs = { - aggregateGuidanceTags, - first: 1, - after: toGlobalId('guidanceTag', 'agg3'), - before: toGlobalId('guidanceTag', 'agg1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - - const aggregateTags = await connectionLoader({ - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - node: { - ...expectedAggregateTags[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - endCursor: toGlobalId( - 'guidanceTag', - expectedAggregateTags[1]._key, - ), - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no tags are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: 5, - } - - const aggregateTags = await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(aggregateTags).toEqual(expectedStructure) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = {} - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: 5, - last: 5, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: -5, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above minimum', () => { - describe('first is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: 500, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `first` limit of 100 records.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 500 for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `last` limit of 100 records.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load Aggregate guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load Aggregate guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = {} - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: 5, - last: 5, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: -5, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above minimum', () => { - describe('first is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - first: 500, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `first` de 100 enregistrements.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 500 for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('throws an error', async () => { - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ) - - const aggregateGuidanceTags = ['agg1', 'agg2'] - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - aggregateGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `last` de 100 enregistrements.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadAggregateGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags.test.js deleted file mode 100644 index e44be7e808..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags.test.js +++ /dev/null @@ -1,403 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadAggregateGuidanceTagByTagId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadAggregateGuidanceTagByTagId function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.aggregateGuidanceTags.save({ - _key: 'agg1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.aggregateGuidanceTags.save({ - _key: 'agg2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single aggregate guidance tag', async () => { - const expectedCursor = await query` - FOR tag IN aggregateGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - const expectedAggregateTag = await expectedCursor.next() - - const loader = loadAggregateGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const aggregateTag = await loader.load(expectedAggregateTag._key) - - expect(aggregateTag).toEqual(expectedAggregateTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple aggregate guidance tags', async () => { - const aggregateTagKeys = [] - const expectedAggregateTags = [] - const expectedCursor = await query` - FOR tag IN aggregateGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempAggregate = await expectedCursor.next() - aggregateTagKeys.push(tempAggregate._key) - expectedAggregateTags.push(tempAggregate) - } - - const loader = loadAggregateGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const aggregateTags = await loader.loadMany(aggregateTagKeys) - expect(aggregateTags).toEqual(expectedAggregateTags) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single aggregate guidance tag', async () => { - const expectedCursor = await query` - FOR tag IN aggregateGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - const expectedAggregateTag = await expectedCursor.next() - - const loader = loadAggregateGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const aggregateTag = await loader.load(expectedAggregateTag._key) - - expect(aggregateTag).toEqual(expectedAggregateTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple aggregate guidance tags', async () => { - const aggregateTagKeys = [] - const expectedAggregateTags = [] - const expectedCursor = await query` - FOR tag IN aggregateGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempAggregate = await expectedCursor.next() - aggregateTagKeys.push(tempAggregate._key) - expectedAggregateTags.push(tempAggregate) - } - - const loader = loadAggregateGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const aggregateTags = await loader.loadMany(aggregateTagKeys) - expect(aggregateTags).toEqual(expectedAggregateTags) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadAggregateGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find Aggregate guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadAggregateGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadAggregateGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find Aggregate guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadAggregateGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadAggregateGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadAggregateGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - - const loader = loadAggregateGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadAggregateGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags-connections.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags-connections.test.js deleted file mode 100644 index a603faf905..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags-connections.test.js +++ /dev/null @@ -1,1941 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadDkimGuidanceTagConnectionsByTagId, - loadDkimGuidanceTagByTagId, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load dkim guidance tag connection function', () => { - let query, drop, truncate, collections, user, i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [''], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [''], - refLinksTechnical: [''], - }, - }) - await collections.dkimGuidanceTags.save({ - _key: 'dkim2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [''], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [''], - refLinksTechnical: [''], - }, - }) - await collections.dkimGuidanceTags.save({ - _key: 'dkim3', - en: { - tagName: 'Some Cool Tag Name C', - guidance: 'Some Cool Guidance C', - refLinksGuide: [''], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo c', - guidance: 'todo c', - refLinksGuide: [''], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns dkim result(s) after a given node id', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - node: { - ...expectedDkimTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dkim result(s) before a given node id', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - node: { - ...expectedDkimTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - node: { - ...expectedDkimTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - node: { - ...expectedDkimTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim1'), - before: toGlobalId('guidanceTag', 'dkim3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim3'), - before: toGlobalId('guidanceTag', 'dkim1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim1'), - before: toGlobalId('guidanceTag', 'dkim3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - - language: 'en', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim3'), - before: toGlobalId('guidanceTag', 'dkim1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim1'), - before: toGlobalId('guidanceTag', 'dkim3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim3'), - before: toGlobalId('guidanceTag', 'dkim1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dkim results are found', () => { - it('returns an empty structure', async () => { - await truncate() - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - first: 5, - } - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns dkim result(s) after a given node id', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - node: { - ...expectedDkimTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dkim result(s) before a given node id', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - node: { - ...expectedDkimTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - node: { - ...expectedDkimTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[0]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - - const dkimTagLoader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTags = await dkimTagLoader.loadMany( - dkimGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - node: { - ...expectedDkimTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTags[1]._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim1'), - before: toGlobalId('guidanceTag', 'dkim3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim3'), - before: toGlobalId('guidanceTag', 'dkim1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim1'), - before: toGlobalId('guidanceTag', 'dkim3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - - language: 'fr', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim3'), - before: toGlobalId('guidanceTag', 'dkim1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim1'), - before: toGlobalId('guidanceTag', 'dkim3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDkimTag = await loader.load('dkim2') - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dkimGuidanceTags: ['dkim1', 'dkim2', 'dkim3'], - first: 5, - after: toGlobalId('guidanceTag', 'dkim3'), - before: toGlobalId('guidanceTag', 'dkim1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDkimTag._key), - node: { - ...expectedDkimTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - endCursor: toGlobalId('guidanceTag', expectedDkimTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dkim results are found', () => { - it('returns an empty structure', async () => { - await truncate() - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - first: 5, - } - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const dkimTags = await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = {} - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `1000` records on the `GuidanceTag` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DKIM guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DKIM guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = {} - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `1000` sur la connexion `GuidanceTag` dépasse la limite `first` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `last` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDkimGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dkimGuidanceTags = ['dkim1', 'dkim2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dkimGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags.test.js deleted file mode 100644 index e47a54a6b8..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags.test.js +++ /dev/null @@ -1,402 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadDkimGuidanceTagByTagId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadDkimGuidanceTagByTagId function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.dkimGuidanceTags.save({ - _key: 'dkim1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.dkimGuidanceTags.save({ - _key: 'dkim2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single dkim guidance tag', async () => { - // Get dkim tag from db - const expectedCursor = await query` - FOR tag IN dkimGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - tagId: tag._key, - id: tag._key - }, - TRANSLATE("en", tag) - ) - ` - const expectedDkimTag = await expectedCursor.next() - - const loader = loadDkimGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const dkim = await loader.load(expectedDkimTag._key) - - expect(dkim).toEqual(expectedDkimTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim guidance tags', async () => { - const dkimTagKeys = [] - const expectedDkimTags = [] - const expectedCursor = await query` - FOR tag IN dkimGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - tagId: tag._key, - id: tag._key - }, - TRANSLATE("en", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempDkim = await expectedCursor.next() - dkimTagKeys.push(tempDkim._key) - expectedDkimTags.push(tempDkim) - } - - const loader = loadDkimGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const dkimTags = await loader.loadMany(dkimTagKeys) - expect(dkimTags).toEqual(expectedDkimTags) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single dkim guidance tag', async () => { - // Get dkim tag from db - const expectedCursor = await query` - FOR tag IN dkimGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - tagId: tag._key, - id: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - const expectedDkimTag = await expectedCursor.next() - - const loader = loadDkimGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const dkim = await loader.load(expectedDkimTag._key) - - expect(dkim).toEqual(expectedDkimTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim guidance tags', async () => { - const dkimTagKeys = [] - const expectedDkimTags = [] - const expectedCursor = await query` - FOR tag IN dkimGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - tagId: tag._key, - id: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempDkim = await expectedCursor.next() - dkimTagKeys.push(tempDkim._key) - expectedDkimTags.push(tempDkim) - } - - const loader = loadDkimGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const dkimTags = await loader.loadMany(dkimTagKeys) - expect(dkimTags).toEqual(expectedDkimTags) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find DKIM guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDkimGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadDkimGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find DKIM guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDkimGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadDkimGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDkimGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadDkimGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDkimGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags-connections.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags-connections.test.js deleted file mode 100644 index 29c5828944..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags-connections.test.js +++ /dev/null @@ -1,1968 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadDmarcGuidanceTagConnectionsByTagId, - loadDmarcGuidanceTagByTagId, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load dmarc guidance tag connection function', () => { - let query, drop, truncate, collections, user, i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(async () => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - - await collections.dmarcGuidanceTags.save({ - _key: 'dmarc1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.dmarcGuidanceTags.save({ - _key: 'dmarc2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.dmarcGuidanceTags.save({ - _key: 'dmarc3', - en: { - tagName: 'Some Cool Tag Name c', - guidance: 'Some Cool Guidance c', - refLinksGuide: [ - { - description: 'IT PIN C', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo c', - guidance: 'todo c', - refLinksGuide: [ - { - description: 'todo c', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns dmarc result(s) after a given node id', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - node: { - ...expectedDmarcTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dmarc result(s) before a given node id', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - node: { - ...expectedDmarcTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - node: { - ...expectedDmarcTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - node: { - ...expectedDmarcTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc1'), - before: toGlobalId('guidanceTag', 'dmarc3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc3'), - before: toGlobalId('guidanceTag', 'dmarc1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const dmarcTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc1'), - before: toGlobalId('guidanceTag', 'dmarc3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc3'), - before: toGlobalId('guidanceTag', 'dmarc1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const dmarcTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc1'), - before: toGlobalId('guidanceTag', 'dmarc3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc3'), - before: toGlobalId('guidanceTag', 'dmarc1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const dmarcTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dmarc results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - first: 5, - } - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns dmarc result(s) after a given node id', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - node: { - ...expectedDmarcTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns dmarc result(s) before a given node id', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - node: { - ...expectedDmarcTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - node: { - ...expectedDmarcTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[0]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - - const dmarcTagLoader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTags = await dmarcTagLoader.loadMany( - dmarcGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - node: { - ...expectedDmarcTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTags[1]._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc1'), - before: toGlobalId('guidanceTag', 'dmarc3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc3'), - before: toGlobalId('guidanceTag', 'dmarc1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const dmarcTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc1'), - before: toGlobalId('guidanceTag', 'dmarc3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc3'), - before: toGlobalId('guidanceTag', 'dmarc1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const dmarcTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc1'), - before: toGlobalId('guidanceTag', 'dmarc3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const dkimTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dkimTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedDmarcTag = await loader.load('dmarc2') - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - dmarcGuidanceTags: ['dmarc1', 'dmarc2', 'dmarc3'], - first: 5, - after: toGlobalId('guidanceTag', 'dmarc3'), - before: toGlobalId('guidanceTag', 'dmarc1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const dmarcTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - node: { - ...expectedDmarcTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - endCursor: toGlobalId('guidanceTag', expectedDmarcTag._key), - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no dmarc results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - first: 5, - } - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const dmarcTags = await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(dmarcTags).toEqual(expectedStructure) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = {} - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `1000` records on the `GuidanceTag` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DMARC guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DMARC guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = {} - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `1000` sur la connexion `GuidanceTag` dépasse la limite `first` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `last` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadDmarcGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const dmarcGuidanceTags = ['dmarc1', 'dmarc2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - dmarcGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags.test.js deleted file mode 100644 index 64f731b7b3..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags.test.js +++ /dev/null @@ -1,402 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadDmarcGuidanceTagByTagId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadDmarcGuidanceTagByTagId function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(async () => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.dmarcGuidanceTags.save({ - _key: 'dmarc1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.dmarcGuidanceTags.save({ - _key: 'dmarc2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single dmarc guidance tag', async () => { - // Get dmarc tag from db - const expectedCursor = await query` - FOR tag IN dmarcGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - const expectedDmarcTag = await expectedCursor.next() - - const loader = loadDmarcGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const dmarcTag = await loader.load(expectedDmarcTag._key) - - expect(dmarcTag).toEqual(expectedDmarcTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dmarc guidance tags', async () => { - const dmarcTagKeys = [] - const expectedDmarcTags = [] - const expectedCursor = await query` - FOR tag IN dmarcGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempDkim = await expectedCursor.next() - dmarcTagKeys.push(tempDkim._key) - expectedDmarcTags.push(tempDkim) - } - - const loader = loadDmarcGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const dmarcTags = await loader.loadMany(dmarcTagKeys) - expect(dmarcTags).toEqual(expectedDmarcTags) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single dmarc guidance tag', async () => { - // Get dmarc tag from db - const expectedCursor = await query` - FOR tag IN dmarcGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - const expectedDmarcTag = await expectedCursor.next() - - const loader = loadDmarcGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const dmarcTag = await loader.load(expectedDmarcTag._key) - - expect(dmarcTag).toEqual(expectedDmarcTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dmarc guidance tags', async () => { - const dmarcTagKeys = [] - const expectedDmarcTags = [] - const expectedCursor = await query` - FOR tag IN dmarcGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempDkim = await expectedCursor.next() - dmarcTagKeys.push(tempDkim._key) - expectedDmarcTags.push(tempDkim) - } - - const loader = loadDmarcGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const dmarcTags = await loader.loadMany(dmarcTagKeys) - expect(dmarcTags).toEqual(expectedDmarcTags) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find DMARC guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDmarcGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadDmarcGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find DMARC guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDmarcGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadDmarcGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadDmarcGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadDmarcGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadDmarcGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-https-guidance-tags-connections.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-https-guidance-tags-connections.test.js deleted file mode 100644 index 04416f20a0..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-https-guidance-tags-connections.test.js +++ /dev/null @@ -1,1966 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadHttpsGuidanceTagConnectionsByTagId, - loadHttpsGuidanceTagByTagId, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load https guidance tag connection function', () => { - let query, drop, truncate, collections, user, i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - await collections.httpsGuidanceTags.save({ - _key: 'https1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.httpsGuidanceTags.save({ - _key: 'https2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.httpsGuidanceTags.save({ - _key: 'https3', - en: { - tagName: 'Some Cool Tag Name C', - guidance: 'Some Cool Guidance C', - refLinksGuide: [ - { - description: 'IT PIN C', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo c', - guidance: 'todo c', - refLinksGuide: [ - { - description: 'todo c', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns https result(s) after a given node id', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - node: { - ...expectedHttpsTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns https result(s) before a given node id', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - node: { - ...expectedHttpsTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - node: { - ...expectedHttpsTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - node: { - ...expectedHttpsTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https1'), - before: toGlobalId('guidanceTag', 'https3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https3'), - before: toGlobalId('guidanceTag', 'https1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https1'), - before: toGlobalId('guidanceTag', 'https3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https3'), - before: toGlobalId('guidanceTag', 'https1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https1'), - before: toGlobalId('guidanceTag', 'https3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https3'), - before: toGlobalId('guidanceTag', 'https1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no https results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - first: 5, - } - - const httpsGuidanceTags = ['https1', 'https2'] - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns https result(s) after a given node id', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - node: { - ...expectedHttpsTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns https result(s) before a given node id', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - node: { - ...expectedHttpsTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - first: 1, - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - node: { - ...expectedHttpsTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[0]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const httpsGuidanceTags = ['https1', 'https2'] - - const httpsTagLoader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTags = await httpsTagLoader.loadMany( - httpsGuidanceTags, - ) - - const connectionArgs = { - last: 1, - } - - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - node: { - ...expectedHttpsTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTags[1]._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https1'), - before: toGlobalId('guidanceTag', 'https3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https3'), - before: toGlobalId('guidanceTag', 'https1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https1'), - before: toGlobalId('guidanceTag', 'https3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https3'), - before: toGlobalId('guidanceTag', 'https1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https1'), - before: toGlobalId('guidanceTag', 'https3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedHttpsTag = await loader.load('https2') - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - httpsGuidanceTags: ['https1', 'https2', 'https3'], - first: 5, - after: toGlobalId('guidanceTag', 'https3'), - before: toGlobalId('guidanceTag', 'https1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const httpsTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - node: { - ...expectedHttpsTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - endCursor: toGlobalId('guidanceTag', expectedHttpsTag._key), - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no https results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - first: 5, - } - - const httpsGuidanceTags = ['https1', 'https2'] - const httpsTags = await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(httpsTags).toEqual(expectedStructure) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = {} - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `1000` records on the `GuidanceTag` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load HTTPS guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load HTTPS guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = {} - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `1000` sur la connexion `GuidanceTag` dépasse la limite `first` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `last` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadHttpsGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsGuidanceTags = ['https1', 'https2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - httpsGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-https-guidance-tags.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-https-guidance-tags.test.js deleted file mode 100644 index bdc2a14aed..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-https-guidance-tags.test.js +++ /dev/null @@ -1,402 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadHttpsGuidanceTagByTagId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadHttpsGuidanceTagByTagId function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.httpsGuidanceTags.save({ - _key: 'https1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.httpsGuidanceTags.save({ - _key: 'https2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single https guidance tag', async () => { - // Get https tag from db - const expectedCursor = await query` - FOR tag IN httpsGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - const expectedHttpsTag = await expectedCursor.next() - - const loader = loadHttpsGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const httpsTag = await loader.load(expectedHttpsTag._key) - - expect(httpsTag).toEqual(expectedHttpsTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple https guidance tags', async () => { - const httpsTagKeys = [] - const expectedHttpsTags = [] - const expectedCursor = await query` - FOR tag IN httpsGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempHttps = await expectedCursor.next() - httpsTagKeys.push(tempHttps._key) - expectedHttpsTags.push(tempHttps) - } - - const loader = loadHttpsGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const httpsTags = await loader.loadMany(httpsTagKeys) - expect(httpsTags).toEqual(expectedHttpsTags) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single https guidance tag', async () => { - // Get https tag from db - const expectedCursor = await query` - FOR tag IN httpsGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - const expectedHttpsTag = await expectedCursor.next() - - const loader = loadHttpsGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const httpsTag = await loader.load(expectedHttpsTag._key) - - expect(httpsTag).toEqual(expectedHttpsTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple https guidance tags', async () => { - const httpsTagKeys = [] - const expectedHttpsTags = [] - const expectedCursor = await query` - FOR tag IN httpsGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempHttps = await expectedCursor.next() - httpsTagKeys.push(tempHttps._key) - expectedHttpsTags.push(tempHttps) - } - - const loader = loadHttpsGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const httpsTags = await loader.loadMany(httpsTagKeys) - expect(httpsTags).toEqual(expectedHttpsTags) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find HTTPS guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadHttpsGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadHttpsGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find HTTPS guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadHttpsGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadHttpsGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadHttpsGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadHttpsGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadHttpsGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags-connections.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags-connections.test.js deleted file mode 100644 index 5bfb99a3fa..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags-connections.test.js +++ /dev/null @@ -1,1952 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadSpfGuidanceTagConnectionsByTagId, - loadSpfGuidanceTagByTagId, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load spf guidance tag connection function', () => { - let query, drop, truncate, collections, user, i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - - await collections.spfGuidanceTags.save({ - _key: 'spf1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.spfGuidanceTags.save({ - _key: 'spf2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.spfGuidanceTags.save({ - _key: 'spf3', - en: { - tagName: 'Some Cool Tag Name C', - guidance: 'Some Cool Guidance C', - refLinksGuide: [ - { - description: 'IT PIN C', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo c', - guidance: 'todo c', - refLinksGuide: [ - { - description: 'todo c', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns spf result(s) after a given node id', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - node: { - ...expectedSpfTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns spf result(s) before a given node id', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - node: { - ...expectedSpfTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - first: 1, - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - node: { - ...expectedSpfTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - last: 1, - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - node: { - ...expectedSpfTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf1'), - before: toGlobalId('guidanceTag', 'spf3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf3'), - before: toGlobalId('guidanceTag', 'spf1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf1'), - before: toGlobalId('guidanceTag', 'spf3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf3'), - before: toGlobalId('guidanceTag', 'spf1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf1'), - before: toGlobalId('guidanceTag', 'spf3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf3'), - before: toGlobalId('guidanceTag', 'spf1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no spf results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - first: 5, - } - - const spfGuidanceTags = ['spf1', 'spf2'] - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns spf result(s) after a given node id', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - node: { - ...expectedSpfTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns spf result(s) before a given node id', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - node: { - ...expectedSpfTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - first: 1, - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - node: { - ...expectedSpfTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[0]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - - const spfTagLoader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTags = await spfTagLoader.loadMany(spfGuidanceTags) - - const connectionArgs = { - last: 1, - } - - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - node: { - ...expectedSpfTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTags[1]._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf1'), - before: toGlobalId('guidanceTag', 'spf3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf3'), - before: toGlobalId('guidanceTag', 'spf1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf1'), - before: toGlobalId('guidanceTag', 'spf3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf3'), - before: toGlobalId('guidanceTag', 'spf1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf1'), - before: toGlobalId('guidanceTag', 'spf3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSpfTag = await loader.load('spf2') - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - spfGuidanceTags: ['spf1', 'spf2', 'spf3'], - first: 5, - after: toGlobalId('guidanceTag', 'spf3'), - before: toGlobalId('guidanceTag', 'spf1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const spfTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSpfTag._key), - node: { - ...expectedSpfTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - endCursor: toGlobalId('guidanceTag', expectedSpfTag._key), - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no spf results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - first: 5, - } - - const spfGuidanceTags = ['spf1', 'spf2'] - const spfTags = await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(spfTags).toEqual(expectedStructure) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = {} - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `1000` records on the `GuidanceTag` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load SPF guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load SPF guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = {} - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `1000` sur la connexion `GuidanceTag` dépasse la limite `first` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `last` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSpfGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSpfGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const spfGuidanceTags = ['spf1', 'spf2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - spfGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags.test.js deleted file mode 100644 index 00912cfe39..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags.test.js +++ /dev/null @@ -1,402 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadSpfGuidanceTagByTagId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadSpfGuidanceTagByTagId function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(async () => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(async () => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.spfGuidanceTags.save({ - _key: 'spf1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.spfGuidanceTags.save({ - _key: 'spf2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single spf guidance tag', async () => { - // Get spf tag from db - const expectedCursor = await query` - FOR tag IN spfGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - const expectedSpfTag = await expectedCursor.next() - - const loader = loadSpfGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const spfTag = await loader.load(expectedSpfTag._key) - - expect(spfTag).toEqual(expectedSpfTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple spf guidance tags', async () => { - const spfTagKeys = [] - const expectedSpfTags = [] - const expectedCursor = await query` - FOR tag IN spfGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempSpf = await expectedCursor.next() - spfTagKeys.push(tempSpf._key) - expectedSpfTags.push(tempSpf) - } - - const loader = loadSpfGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const spfTags = await loader.loadMany(spfTagKeys) - expect(spfTags).toEqual(expectedSpfTags) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single spf guidance tag', async () => { - // Get spf tag from db - const expectedCursor = await query` - FOR tag IN spfGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - const expectedSpfTag = await expectedCursor.next() - - const loader = loadSpfGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const spfTag = await loader.load(expectedSpfTag._key) - - expect(spfTag).toEqual(expectedSpfTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple spf guidance tags', async () => { - const spfTagKeys = [] - const expectedSpfTags = [] - const expectedCursor = await query` - FOR tag IN spfGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempSpf = await expectedCursor.next() - spfTagKeys.push(tempSpf._key) - expectedSpfTags.push(tempSpf) - } - - const loader = loadSpfGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const spfTags = await loader.loadMany(spfTagKeys) - expect(spfTags).toEqual(expectedSpfTags) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find SPF guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSpfGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadSpfGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find SPF guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSpfGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadSpfGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSpfGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadSpfGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSpfGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags-connections.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags-connections.test.js deleted file mode 100644 index f36e544291..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags-connections.test.js +++ /dev/null @@ -1,1952 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { - loadSslGuidanceTagConnectionsByTagId, - loadSslGuidanceTagByTagId, -} from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('when given the load ssl guidance tag connection function', () => { - let query, drop, truncate, collections, user, i18n - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - - await collections.sslGuidanceTags.save({ - _key: 'ssl1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.sslGuidanceTags.save({ - _key: 'ssl2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.sslGuidanceTags.save({ - _key: 'ssl3', - en: { - tagName: 'Some Cool Tag Name C', - guidance: 'Some Cool Guidance C', - refLinksGuide: [ - { - description: 'IT PIN C', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo c', - guidance: 'todo c', - refLinksGuide: [ - { - description: 'todo c', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns ssl result(s) after a given node id', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedSslTags[0]._key), - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - node: { - ...expectedSslTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns ssl result(s) before a given node id', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedSslTags[1]._key), - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - node: { - ...expectedSslTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - first: 1, - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - node: { - ...expectedSslTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - last: 1, - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - node: { - ...expectedSslTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl1'), - before: toGlobalId('guidanceTag', 'ssl3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl3'), - before: toGlobalId('guidanceTag', 'ssl1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl1'), - before: toGlobalId('guidanceTag', 'ssl3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl3'), - before: toGlobalId('guidanceTag', 'ssl1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl1'), - before: toGlobalId('guidanceTag', 'ssl3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'en', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl3'), - before: toGlobalId('guidanceTag', 'ssl1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no ssl results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'en', - }) - - const connectionArgs = { - first: 5, - } - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('using after cursor', () => { - it('returns ssl result(s) after a given node id', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - first: 5, - after: toGlobalId('guidanceTag', expectedSslTags[0]._key), - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - node: { - ...expectedSslTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns ssl result(s) before a given node id', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - first: 5, - before: toGlobalId('guidanceTag', expectedSslTags[1]._key), - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - node: { - ...expectedSslTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of item(s)', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - first: 1, - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - node: { - ...expectedSslTags[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[0]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of item(s)', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - - const sslTagLoader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTags = await sslTagLoader.loadMany(sslGuidanceTags) - - const connectionArgs = { - last: 1, - } - - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - node: { - ...expectedSslTags[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - endCursor: toGlobalId('guidanceTag', expectedSslTags[1]._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('using orderBy field', () => { - describe('ordering on TAG_ID', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl1'), - before: toGlobalId('guidanceTag', 'ssl3'), - orderBy: { - field: 'tag-id', - direction: 'ASC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl3'), - before: toGlobalId('guidanceTag', 'ssl1'), - orderBy: { - field: 'tag-id', - direction: 'DESC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TAG_NAME', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl1'), - before: toGlobalId('guidanceTag', 'ssl3'), - orderBy: { - field: 'tag-name', - direction: 'ASC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl3'), - before: toGlobalId('guidanceTag', 'ssl1'), - orderBy: { - field: 'tag-name', - direction: 'DESC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on GUIDANCE', () => { - describe('order is set to ASC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl1'), - before: toGlobalId('guidanceTag', 'ssl3'), - orderBy: { - field: 'guidance', - direction: 'ASC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns guidance tag', async () => { - const loader = loadSslGuidanceTagByTagId({ - query, - language: 'fr', - }) - const expectedSslTag = await loader.load('ssl2') - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - sslGuidanceTags: ['ssl1', 'ssl2', 'ssl3'], - first: 5, - after: toGlobalId('guidanceTag', 'ssl3'), - before: toGlobalId('guidanceTag', 'ssl1'), - orderBy: { - field: 'guidance', - direction: 'DESC', - }, - } - const sslTags = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('guidanceTag', expectedSslTag._key), - node: { - ...expectedSslTag, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('guidanceTag', expectedSslTag._key), - endCursor: toGlobalId('guidanceTag', expectedSslTag._key), - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no ssl results are found', () => { - beforeEach(async () => { - await truncate() - }) - it('returns an empty structure', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - language: 'fr', - }) - - const connectionArgs = { - first: 5, - } - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const sslTags = await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(sslTags).toEqual(expectedStructure) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = {} - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `GuidanceTag` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `1000` records on the `GuidanceTag` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting `500` records on the `GuidanceTag` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load SSL guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load SSL guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = {} - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('both limits are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 1000, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `1000` sur la connexion `GuidanceTag` dépasse la limite `first` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 1000 for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - describe('last is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "La demande d'enregistrements `500` sur la connexion `GuidanceTag` dépasse la limite `last` de 100 enregistrements.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSslGuidanceTagConnectionsByTagId.`, - ]) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSslGuidanceTagConnectionsByTagId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslGuidanceTags = ['ssl1', 'ssl2'] - const connectionArgs = { - first: 5, - } - try { - await connectionLoader({ - sslGuidanceTags, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags.test.js b/api-js/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags.test.js deleted file mode 100644 index f3e7961bc9..0000000000 --- a/api-js/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags.test.js +++ /dev/null @@ -1,402 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadSslGuidanceTagByTagId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadSslGuidanceTagByTagId function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.sslGuidanceTags.save({ - _key: 'ssl1', - en: { - tagName: 'Some Cool Tag Name A', - guidance: 'Some Cool Guidance A', - refLinksGuide: [ - { - description: 'IT PIN A', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo a', - guidance: 'todo a', - refLinksGuide: [ - { - description: 'todo a', - }, - ], - refLinksTechnical: [''], - }, - }) - await collections.sslGuidanceTags.save({ - _key: 'ssl2', - en: { - tagName: 'Some Cool Tag Name B', - guidance: 'Some Cool Guidance B', - refLinksGuide: [ - { - description: 'IT PIN B', - }, - ], - refLinksTechnical: [''], - }, - fr: { - tagName: 'todo b', - guidance: 'todo b', - refLinksGuide: [ - { - description: 'todo b', - }, - ], - refLinksTechnical: [''], - }, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single dkim guidance tag', async () => { - // Get ssl tag from db - const expectedCursor = await query` - FOR tag IN sslGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - const expectedSslTag = await expectedCursor.next() - - const loader = loadSslGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const sslTag = await loader.load(expectedSslTag._key) - - expect(sslTag).toEqual(expectedSslTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim guidance tags', async () => { - const sslTagKeys = [] - const expectedSslTags = [] - const expectedCursor = await query` - FOR tag IN sslGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("en", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempSsl = await expectedCursor.next() - sslTagKeys.push(tempSsl._key) - expectedSslTags.push(tempSsl) - } - - const loader = loadSslGuidanceTagByTagId({ - query, - i18n, - language: 'en', - }) - const sslTags = await loader.loadMany(sslTagKeys) - expect(sslTags).toEqual(expectedSslTags) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a single id', () => { - it('returns a single dkim guidance tag', async () => { - // Get ssl tag from db - const expectedCursor = await query` - FOR tag IN sslGuidanceTags - SORT tag._key ASC LIMIT 1 - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - const expectedSslTag = await expectedCursor.next() - - const loader = loadSslGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const sslTag = await loader.load(expectedSslTag._key) - - expect(sslTag).toEqual(expectedSslTag) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim guidance tags', async () => { - const sslTagKeys = [] - const expectedSslTags = [] - const expectedCursor = await query` - FOR tag IN sslGuidanceTags - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE("fr", tag) - ) - ` - - while (expectedCursor.hasMore) { - const tempSsl = await expectedCursor.next() - sslTagKeys.push(tempSsl._key) - expectedSslTags.push(tempSsl) - } - - const loader = loadSslGuidanceTagByTagId({ - query, - i18n, - language: 'fr', - }) - const sslTags = await loader.loadMany(sslTagKeys) - expect(sslTags).toEqual(expectedSslTags) - }) - }) - }) - }) - - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadSslGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find SSL guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSslGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadSslGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find SSL guidance tag(s). Please try again.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSslGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - const loader = loadSslGuidanceTagByTagId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSslGuidanceTagByTagId: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const mockedCursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - const loader = loadSslGuidanceTagByTagId({ - query: jest.fn().mockReturnValue(mockedCursor), - userKey: '1234', - i18n, - }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.", - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSslGuidanceTagByTagId: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/guidance-tag/loaders/index.js b/api-js/src/guidance-tag/loaders/index.js deleted file mode 100644 index ea70fd106b..0000000000 --- a/api-js/src/guidance-tag/loaders/index.js +++ /dev/null @@ -1,12 +0,0 @@ -export * from './load-aggregate-guidance-tags' -export * from './load-aggregate-guidance-tags-connections' -export * from './load-dkim-guidance-tags' -export * from './load-dkim-guidance-tags-connections' -export * from './load-dmarc-guidance-tags' -export * from './load-dmarc-guidance-tags-connections' -export * from './load-https-guidance-tags' -export * from './load-https-guidance-tags-connections' -export * from './load-spf-guidance-tags' -export * from './load-spf-guidance-tags-connections' -export * from './load-ssl-guidance-tags' -export * from './load-ssl-guidance-tags-connections' diff --git a/api-js/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js b/api-js/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js deleted file mode 100644 index 2015f70f4d..0000000000 --- a/api-js/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js +++ /dev/null @@ -1,301 +0,0 @@ -import { t } from '@lingui/macro' -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' - -export const loadAggregateGuidanceTagConnectionsByTagId = - ({ query, userKey, cleanseInput, i18n, language }) => - async ({ aggregateGuidanceTags, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` - } else { - let afterTemplateDirection = aql`<` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } - - afterVar = aql`LET afterVar = DOCUMENT(aggregateGuidanceTags, ${afterId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`afterVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, afterVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, afterVar).guidance` - } - - afterTemplate = aql` - FILTER ${tagField} ${afterTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` - } else { - let beforeTemplateDirection = aql`>` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(aggregateGuidanceTags, ${beforeId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`beforeVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, beforeVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, beforeVar).guidance` - } - - beforeTemplate = aql` - FILTER ${tagField} ${beforeTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAggregateGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAggregateGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])` - - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`<` - let hasPreviousPageDirection = aql`>` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } - - let tagField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - hasNextPageDocument = aql`LAST(retrievedAggregateGuidanceTags)._key` - hasPreviousPageDocument = aql`FIRST(retrievedAggregateGuidanceTags)._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - hasNextPageDocument = aql`LAST(retrievedAggregateGuidanceTags).tagName` - hasPreviousPageDocument = aql`FIRST(retrievedAggregateGuidanceTags).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - hasNextPageDocument = aql`LAST(retrievedAggregateGuidanceTags).guidance` - hasPreviousPageDocument = aql`FIRST(retrievedAggregateGuidanceTags).guidance` - } - - hasNextPageFilter = aql` - FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${tagField} == ${hasNextPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])) - ` - - hasPreviousPageFilter = aql` - FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${tagField} == ${hasPreviousPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - sortByField = aql`tag._key ${orderBy.direction},` - } else if (orderBy.field === 'tag-name') { - sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` - } else if (orderBy.field === 'guidance') { - sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let aggregateGuidanceTagInfoCursor - try { - aggregateGuidanceTagInfoCursor = await query` - WITH aggregateGuidanceTags - - ${afterVar} - ${beforeVar} - - LET retrievedAggregateGuidanceTags = ( - FOR tag IN aggregateGuidanceTags - FILTER tag._key IN ${aggregateGuidanceTags} - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE(${language}, tag) - ) - ) - - LET hasNextPage = (LENGTH( - FOR tag IN aggregateGuidanceTags - FILTER tag._key IN ${aggregateGuidanceTags} - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR tag IN aggregateGuidanceTags - FILTER tag._key IN ${aggregateGuidanceTags} - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - RETURN { - "aggregateGuidanceTags": retrievedAggregateGuidanceTags, - "totalCount": LENGTH(${aggregateGuidanceTags}), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedAggregateGuidanceTags)._key, - "endKey": LAST(retrievedAggregateGuidanceTags)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load Aggregate guidance tag(s). Please try again.`), - ) - } - - let aggregateGuidanceTagInfo - try { - aggregateGuidanceTagInfo = await aggregateGuidanceTagInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load Aggregate guidance tag(s). Please try again.`), - ) - } - - if (aggregateGuidanceTagInfo.aggregateGuidanceTags.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = aggregateGuidanceTagInfo.aggregateGuidanceTags.map((tag) => ({ - cursor: toGlobalId('guidanceTag', tag._key), - node: tag, - })) - - return { - edges, - totalCount: aggregateGuidanceTagInfo.totalCount, - pageInfo: { - hasNextPage: aggregateGuidanceTagInfo.hasNextPage, - hasPreviousPage: aggregateGuidanceTagInfo.hasPreviousPage, - startCursor: toGlobalId( - 'guidanceTag', - aggregateGuidanceTagInfo.startKey, - ), - endCursor: toGlobalId('guidanceTag', aggregateGuidanceTagInfo.endKey), - }, - } - } diff --git a/api-js/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js b/api-js/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js deleted file mode 100644 index 08ff18f97c..0000000000 --- a/api-js/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js +++ /dev/null @@ -1,304 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDkimGuidanceTagConnectionsByTagId = - ({ query, userKey, cleanseInput, i18n, language }) => - async ({ dkimGuidanceTags, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dkimGuidanceTags, ${afterId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`afterVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, afterVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, afterVar).guidance` - } - - afterTemplate = aql` - FILTER ${tagField} ${afterTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dkimGuidanceTags, ${beforeId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`beforeVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, beforeVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, beforeVar).guidance` - } - - beforeTemplate = aql` - FILTER ${tagField} ${beforeTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDkimGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDkimGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let tagField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - hasNextPageDocument = aql`LAST(retrievedDkimGuidanceTags)._key` - hasPreviousPageDocument = aql`FIRST(retrievedDkimGuidanceTags)._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - hasNextPageDocument = aql`LAST(retrievedDkimGuidanceTags).tagName` - hasPreviousPageDocument = aql`FIRST(retrievedDkimGuidanceTags).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - hasNextPageDocument = aql`LAST(retrievedDkimGuidanceTags).guidance` - hasPreviousPageDocument = aql`FIRST(retrievedDkimGuidanceTags).guidance` - } - - hasNextPageFilter = aql` - FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${tagField} == ${hasNextPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])) - ` - - hasPreviousPageFilter = aql` - FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${tagField} == ${hasPreviousPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - sortByField = aql`tag._key ${orderBy.direction},` - } else if (orderBy.field === 'tag-name') { - sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` - } else if (orderBy.field === 'guidance') { - sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let dkimGuidanceTagInfoCursor - try { - dkimGuidanceTagInfoCursor = await query` - WITH dkimGuidanceTags - - ${afterVar} - ${beforeVar} - - LET retrievedDkimGuidanceTags = ( - FOR tag IN dkimGuidanceTags - FILTER tag._key IN ${dkimGuidanceTags} - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE(${language}, tag) - ) - ) - - LET hasNextPage = (LENGTH( - FOR tag IN dkimGuidanceTags - FILTER tag._key IN ${dkimGuidanceTags} - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR tag IN dkimGuidanceTags - FILTER tag._key IN ${dkimGuidanceTags} - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - RETURN { - "dkimGuidanceTags": retrievedDkimGuidanceTags, - "totalCount": LENGTH(${dkimGuidanceTags}), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDkimGuidanceTags)._key, - "endKey": LAST(retrievedDkimGuidanceTags)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DKIM guidance tag(s). Please try again.`), - ) - } - - let dkimGuidanceTagInfo - try { - dkimGuidanceTagInfo = await dkimGuidanceTagInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DKIM guidance tag(s). Please try again.`), - ) - } - - if (dkimGuidanceTagInfo.dkimGuidanceTags.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = dkimGuidanceTagInfo.dkimGuidanceTags.map((tag) => ({ - cursor: toGlobalId('guidanceTag', tag._key), - node: tag, - })) - - return { - edges, - totalCount: dkimGuidanceTagInfo.totalCount, - pageInfo: { - hasNextPage: dkimGuidanceTagInfo.hasNextPage, - hasPreviousPage: dkimGuidanceTagInfo.hasPreviousPage, - startCursor: toGlobalId('guidanceTag', dkimGuidanceTagInfo.startKey), - endCursor: toGlobalId('guidanceTag', dkimGuidanceTagInfo.endKey), - }, - } - } diff --git a/api-js/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js b/api-js/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js deleted file mode 100644 index 27bf131ea5..0000000000 --- a/api-js/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js +++ /dev/null @@ -1,304 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadDmarcGuidanceTagConnectionsByTagId = - ({ query, userKey, cleanseInput, i18n, language }) => - async ({ dmarcGuidanceTags, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dmarcGuidanceTags, ${afterId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`afterVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, afterVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, afterVar).guidance` - } - - afterTemplate = aql` - FILTER ${tagField} ${afterTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dmarcGuidanceTags, ${beforeId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`beforeVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, beforeVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, beforeVar).guidance` - } - - beforeTemplate = aql` - FILTER ${tagField} ${beforeTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDmarcGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let tagField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - hasNextPageDocument = aql`LAST(retrievedDmarcGuidanceTags)._key` - hasPreviousPageDocument = aql`FIRST(retrievedDmarcGuidanceTags)._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - hasNextPageDocument = aql`LAST(retrievedDmarcGuidanceTags).tagName` - hasPreviousPageDocument = aql`FIRST(retrievedDmarcGuidanceTags).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - hasNextPageDocument = aql`LAST(retrievedDmarcGuidanceTags).guidance` - hasPreviousPageDocument = aql`FIRST(retrievedDmarcGuidanceTags).guidance` - } - - hasNextPageFilter = aql` - FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${tagField} == ${hasNextPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])) - ` - - hasPreviousPageFilter = aql` - FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${tagField} == ${hasPreviousPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - sortByField = aql`tag._key ${orderBy.direction},` - } else if (orderBy.field === 'tag-name') { - sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` - } else if (orderBy.field === 'guidance') { - sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let dmarcGuidanceTagInfoCursor - try { - dmarcGuidanceTagInfoCursor = await query` - WITH dmarcGuidanceTags - - ${afterVar} - ${beforeVar} - - LET retrievedDmarcGuidanceTags = ( - FOR tag IN dmarcGuidanceTags - FILTER tag._key IN ${dmarcGuidanceTags} - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE(${language}, tag) - ) - ) - - LET hasNextPage = (LENGTH( - FOR tag IN dmarcGuidanceTags - FILTER tag._key IN ${dmarcGuidanceTags} - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR tag IN dmarcGuidanceTags - FILTER tag._key IN ${dmarcGuidanceTags} - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - RETURN { - "dmarcGuidanceTags": retrievedDmarcGuidanceTags, - "totalCount": LENGTH(${dmarcGuidanceTags}), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedDmarcGuidanceTags)._key, - "endKey": LAST(retrievedDmarcGuidanceTags)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DMARC guidance tag(s). Please try again.`), - ) - } - - let dmarcGuidanceTagInfo - try { - dmarcGuidanceTagInfo = await dmarcGuidanceTagInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DMARC guidance tag(s). Please try again.`), - ) - } - - if (dmarcGuidanceTagInfo.dmarcGuidanceTags.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = dmarcGuidanceTagInfo.dmarcGuidanceTags.map((tag) => ({ - cursor: toGlobalId('guidanceTag', tag._key), - node: tag, - })) - - return { - edges, - totalCount: dmarcGuidanceTagInfo.totalCount, - pageInfo: { - hasNextPage: dmarcGuidanceTagInfo.hasNextPage, - hasPreviousPage: dmarcGuidanceTagInfo.hasPreviousPage, - startCursor: toGlobalId('guidanceTag', dmarcGuidanceTagInfo.startKey), - endCursor: toGlobalId('guidanceTag', dmarcGuidanceTagInfo.endKey), - }, - } - } diff --git a/api-js/src/guidance-tag/loaders/load-https-guidance-tags-connections.js b/api-js/src/guidance-tag/loaders/load-https-guidance-tags-connections.js deleted file mode 100644 index 09b9c1a1af..0000000000 --- a/api-js/src/guidance-tag/loaders/load-https-guidance-tags-connections.js +++ /dev/null @@ -1,304 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadHttpsGuidanceTagConnectionsByTagId = - ({ query, userKey, cleanseInput, i18n, language }) => - async ({ httpsGuidanceTags, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(httpsGuidanceTags, ${afterId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`afterVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, afterVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, afterVar).guidance` - } - - afterTemplate = aql` - FILTER ${tagField} ${afterTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(httpsGuidanceTags, ${beforeId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`beforeVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, beforeVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, beforeVar).guidance` - } - - beforeTemplate = aql` - FILTER ${tagField} ${beforeTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadHttpsGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadHttpsGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let tagField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - hasNextPageDocument = aql`LAST(retrievedHttpsGuidanceTags)._key` - hasPreviousPageDocument = aql`FIRST(retrievedHttpsGuidanceTags)._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - hasNextPageDocument = aql`LAST(retrievedHttpsGuidanceTags).tagName` - hasPreviousPageDocument = aql`FIRST(retrievedHttpsGuidanceTags).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - hasNextPageDocument = aql`LAST(retrievedHttpsGuidanceTags).guidance` - hasPreviousPageDocument = aql`FIRST(retrievedHttpsGuidanceTags).guidance` - } - - hasNextPageFilter = aql` - FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${tagField} == ${hasNextPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])) - ` - - hasPreviousPageFilter = aql` - FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${tagField} == ${hasPreviousPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - sortByField = aql`tag._key ${orderBy.direction},` - } else if (orderBy.field === 'tag-name') { - sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` - } else if (orderBy.field === 'guidance') { - sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let httpsGuidanceTagInfoCursor - try { - httpsGuidanceTagInfoCursor = await query` - WITH httpsGuidanceTags - - ${afterVar} - ${beforeVar} - - LET retrievedHttpsGuidanceTags = ( - FOR tag IN httpsGuidanceTags - FILTER tag._key IN ${httpsGuidanceTags} - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE(${language}, tag) - ) - ) - - LET hasNextPage = (LENGTH( - FOR tag IN httpsGuidanceTags - FILTER tag._key IN ${httpsGuidanceTags} - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR tag IN httpsGuidanceTags - FILTER tag._key IN ${httpsGuidanceTags} - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - RETURN { - "httpsGuidanceTags": retrievedHttpsGuidanceTags, - "totalCount": LENGTH(${httpsGuidanceTags}), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedHttpsGuidanceTags)._key, - "endKey": LAST(retrievedHttpsGuidanceTags)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load HTTPS guidance tag(s). Please try again.`), - ) - } - - let httpsGuidanceTagInfo - try { - httpsGuidanceTagInfo = await httpsGuidanceTagInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load HTTPS guidance tag(s). Please try again.`), - ) - } - - if (httpsGuidanceTagInfo.httpsGuidanceTags.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = httpsGuidanceTagInfo.httpsGuidanceTags.map((tag) => ({ - cursor: toGlobalId('guidanceTag', tag._key), - node: tag, - })) - - return { - edges, - totalCount: httpsGuidanceTagInfo.totalCount, - pageInfo: { - hasNextPage: httpsGuidanceTagInfo.hasNextPage, - hasPreviousPage: httpsGuidanceTagInfo.hasPreviousPage, - startCursor: toGlobalId('guidanceTag', httpsGuidanceTagInfo.startKey), - endCursor: toGlobalId('guidanceTag', httpsGuidanceTagInfo.endKey), - }, - } - } diff --git a/api-js/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js b/api-js/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js deleted file mode 100644 index d131630ce7..0000000000 --- a/api-js/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js +++ /dev/null @@ -1,304 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadSpfGuidanceTagConnectionsByTagId = - ({ query, userKey, cleanseInput, i18n, language }) => - async ({ spfGuidanceTags, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(spfGuidanceTags, ${afterId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`afterVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, afterVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, afterVar).guidance` - } - - afterTemplate = aql` - FILTER ${tagField} ${afterTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(spfGuidanceTags, ${beforeId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`beforeVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, beforeVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, beforeVar).guidance` - } - - beforeTemplate = aql` - FILTER ${tagField} ${beforeTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSpfGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadSpfGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let tagField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - hasNextPageDocument = aql`LAST(retrievedSpfGuidanceTags)._key` - hasPreviousPageDocument = aql`FIRST(retrievedSpfGuidanceTags)._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - hasNextPageDocument = aql`LAST(retrievedSpfGuidanceTags).tagName` - hasPreviousPageDocument = aql`FIRST(retrievedSpfGuidanceTags).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - hasNextPageDocument = aql`LAST(retrievedSpfGuidanceTags).guidance` - hasPreviousPageDocument = aql`FIRST(retrievedSpfGuidanceTags).guidance` - } - - hasNextPageFilter = aql` - FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${tagField} == ${hasNextPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])) - ` - - hasPreviousPageFilter = aql` - FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${tagField} == ${hasPreviousPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - sortByField = aql`tag._key ${orderBy.direction},` - } else if (orderBy.field === 'tag-name') { - sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` - } else if (orderBy.field === 'guidance') { - sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let spfGuidanceTagInfoCursor - try { - spfGuidanceTagInfoCursor = await query` - WITH spfGuidanceTags - - ${afterVar} - ${beforeVar} - - LET retrievedSpfGuidanceTags = ( - FOR tag IN spfGuidanceTags - FILTER tag._key IN ${spfGuidanceTags} - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE(${language}, tag) - ) - ) - - LET hasNextPage = (LENGTH( - FOR tag IN spfGuidanceTags - FILTER tag._key IN ${spfGuidanceTags} - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR tag IN spfGuidanceTags - FILTER tag._key IN ${spfGuidanceTags} - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - RETURN { - "spfGuidanceTags": retrievedSpfGuidanceTags, - "totalCount": LENGTH(${spfGuidanceTags}), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedSpfGuidanceTags)._key, - "endKey": LAST(retrievedSpfGuidanceTags)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load SPF guidance tag(s). Please try again.`), - ) - } - - let spfGuidanceTagInfo - try { - spfGuidanceTagInfo = await spfGuidanceTagInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load SPF guidance tag(s). Please try again.`), - ) - } - - if (spfGuidanceTagInfo.spfGuidanceTags.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = spfGuidanceTagInfo.spfGuidanceTags.map((tag) => ({ - cursor: toGlobalId('guidanceTag', tag._key), - node: tag, - })) - - return { - edges, - totalCount: spfGuidanceTagInfo.totalCount, - pageInfo: { - hasNextPage: spfGuidanceTagInfo.hasNextPage, - hasPreviousPage: spfGuidanceTagInfo.hasPreviousPage, - startCursor: toGlobalId('guidanceTag', spfGuidanceTagInfo.startKey), - endCursor: toGlobalId('guidanceTag', spfGuidanceTagInfo.endKey), - }, - } - } diff --git a/api-js/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js b/api-js/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js deleted file mode 100644 index c9af366d3a..0000000000 --- a/api-js/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js +++ /dev/null @@ -1,304 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadSslGuidanceTagConnectionsByTagId = - ({ query, userKey, cleanseInput, i18n, language }) => - async ({ sslGuidanceTags, after, before, first, last, orderBy }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` - } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(sslGuidanceTags, ${afterId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`afterVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, afterVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, afterVar).guidance` - } - - afterTemplate = aql` - FILTER ${tagField} ${afterTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` - } else { - let beforeTemplateDirection - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(sslGuidanceTags, ${beforeId})` - - let tagField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - documentField = aql`beforeVar._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - documentField = aql`TRANSLATE(${language}, beforeVar).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - documentField = aql`TRANSLATE(${language}, beforeVar).guidance` - } - - beforeTemplate = aql` - FILTER ${tagField} ${beforeTemplateDirection} ${documentField} - OR (${tagField} == ${documentField} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) - ` - } - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSslGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadSslGuidanceTagConnectionsByTagId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection - let hasPreviousPageDirection - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let tagField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - tagField = aql`tag._key` - hasNextPageDocument = aql`LAST(retrievedSslGuidanceTags)._key` - hasPreviousPageDocument = aql`FIRST(retrievedSslGuidanceTags)._key` - } else if (orderBy.field === 'tag-name') { - tagField = aql`TRANSLATE(${language}, tag).tagName` - hasNextPageDocument = aql`LAST(retrievedSslGuidanceTags).tagName` - hasPreviousPageDocument = aql`FIRST(retrievedSslGuidanceTags).tagName` - } else if (orderBy.field === 'guidance') { - tagField = aql`TRANSLATE(${language}, tag).guidance` - hasNextPageDocument = aql`LAST(retrievedSslGuidanceTags).guidance` - hasPreviousPageDocument = aql`FIRST(retrievedSslGuidanceTags).guidance` - } - - hasNextPageFilter = aql` - FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${tagField} == ${hasNextPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])) - ` - - hasPreviousPageFilter = aql` - FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${tagField} == ${hasPreviousPageDocument} - AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'tag-id') { - sortByField = aql`tag._key ${orderBy.direction},` - } else if (orderBy.field === 'tag-name') { - sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` - } else if (orderBy.field === 'guidance') { - sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let sslGuidanceTagInfoCursor - try { - sslGuidanceTagInfoCursor = await query` - WITH sslGuidanceTags - - ${afterVar} - ${beforeVar} - - LET retrievedSslGuidanceTags = ( - FOR tag IN sslGuidanceTags - FILTER tag._key IN ${sslGuidanceTags} - ${afterTemplate} - ${beforeTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE( - { - _id: tag._id, - _key: tag._key, - _rev: tag._rev, - _type: "guidanceTag", - id: tag._key, - tagId: tag._key - }, - TRANSLATE(${language}, tag) - ) - ) - - LET hasNextPage = (LENGTH( - FOR tag IN sslGuidanceTags - FILTER tag._key IN ${sslGuidanceTags} - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR tag IN sslGuidanceTags - FILTER tag._key IN ${sslGuidanceTags} - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 - RETURN tag - ) > 0 ? true : false) - - RETURN { - "sslGuidanceTags": retrievedSslGuidanceTags, - "totalCount": LENGTH(${sslGuidanceTags}), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedSslGuidanceTags)._key, - "endKey": LAST(retrievedSslGuidanceTags)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load SSL guidance tag(s). Please try again.`), - ) - } - - let sslGuidanceTagInfo - try { - sslGuidanceTagInfo = await sslGuidanceTagInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load SSL guidance tag(s). Please try again.`), - ) - } - - if (sslGuidanceTagInfo.sslGuidanceTags.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = sslGuidanceTagInfo.sslGuidanceTags.map((tag) => ({ - cursor: toGlobalId('guidanceTag', tag._key), - node: tag, - })) - - return { - edges, - totalCount: sslGuidanceTagInfo.totalCount, - pageInfo: { - hasNextPage: sslGuidanceTagInfo.hasNextPage, - hasPreviousPage: sslGuidanceTagInfo.hasPreviousPage, - startCursor: toGlobalId('guidanceTag', sslGuidanceTagInfo.startKey), - endCursor: toGlobalId('guidanceTag', sslGuidanceTagInfo.endKey), - }, - } - } diff --git a/api-js/src/guidance-tag/objects/guidance-tag-connection.js b/api-js/src/guidance-tag/objects/guidance-tag-connection.js deleted file mode 100644 index 1bc28e89f1..0000000000 --- a/api-js/src/guidance-tag/objects/guidance-tag-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { guidanceTagType } from './guidance-tag' - -export const guidanceTagConnection = connectionDefinitions({ - name: 'GuidanceTag', - nodeType: guidanceTagType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of guidance tags for a given scan type.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/guidance-tag/objects/guidance-tag.js b/api-js/src/guidance-tag/objects/guidance-tag.js deleted file mode 100644 index c18635b29d..0000000000 --- a/api-js/src/guidance-tag/objects/guidance-tag.js +++ /dev/null @@ -1,41 +0,0 @@ -import { GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' -import { globalIdField } from 'graphql-relay' - -import { refLinksType } from './ref-links' -import { nodeInterface } from '../../node' - -export const guidanceTagType = new GraphQLObjectType({ - name: 'GuidanceTag', - description: - 'Details for a given guidance tag based on https://github.com/canada-ca/tracker/wiki/Guidance-Tags', - fields: () => ({ - id: globalIdField('guidanceTag'), - tagId: { - type: GraphQLString, - description: 'The guidance tag ID.', - resolve: ({ tagId }) => tagId, - }, - tagName: { - type: GraphQLString, - description: 'The guidance tag name.', - resolve: ({ tagName }) => tagName, - }, - guidance: { - type: GraphQLString, - description: - 'Guidance for changes to record, or to maintain current stance.', - resolve: ({ guidance }) => guidance, - }, - refLinks: { - type: GraphQLList(refLinksType), - description: 'Links to implementation guidance for a given tag.', - resolve: ({ refLinksGuide }) => refLinksGuide, - }, - refLinksTech: { - type: GraphQLList(refLinksType), - description: 'Links to technical information for a given tag.', - resolve: ({ refLinksTechnical }) => refLinksTechnical, - }, - }), - interfaces: [nodeInterface], -}) diff --git a/api-js/src/guidance-tag/objects/ref-links.js b/api-js/src/guidance-tag/objects/ref-links.js deleted file mode 100644 index 73bfe6faad..0000000000 --- a/api-js/src/guidance-tag/objects/ref-links.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' - -export const refLinksType = new GraphQLObjectType({ - name: 'RefLinks', - description: - 'Object containing the information of various links for guidance or technical documentation.', - fields: () => ({ - description: { - type: GraphQLString, - description: 'Title of the guidance link.', - resolve: ({ description }) => description, - }, - refLink: { - type: GraphQLString, - description: 'URL for the guidance documentation.', - resolve: ({ ref_link: refLink }) => refLink, - }, - }), -}) diff --git a/api-js/src/locale/en/messages.js b/api-js/src/locale/en/messages.js deleted file mode 100644 index 045c90632a..0000000000 --- a/api-js/src/locale/en/messages.js +++ /dev/null @@ -1,674 +0,0 @@ -/*eslint-disable*/ module.exports = { - messages: { - 'Authentication error. Please sign in.': - 'Authentication error. Please sign in.', - 'Cannot query affiliations on organization without admin permission or higher.': - 'Cannot query affiliations on organization without admin permission or higher.', - 'Email already in use.': 'Email already in use.', - 'If an account with this username is found, a password reset link will be found in your inbox.': - 'If an account with this username is found, a password reset link will be found in your inbox.', - 'If an account with this username is found, an email verification link will be found in your inbox.': - 'If an account with this username is found, an email verification link will be found in your inbox.', - 'Incorrect TFA code. Please sign in again.': - 'Incorrect TFA code. Please sign in again.', - 'Incorrect token value. Please request a new email.': - 'Incorrect token value. Please request a new email.', - 'Incorrect username or password. Please try again.': - 'Incorrect username or password. Please try again.', - 'Invalid token, please sign in.': 'Invalid token, please sign in.', - 'New passwords do not match.': 'New passwords do not match.', - 'No organization with the provided slug could be found.': - 'No organization with the provided slug could be found.', - 'No verified domain with the provided domain could be found.': - 'No verified domain with the provided domain could be found.', - 'No verified organization with the provided slug could be found.': - 'No verified organization with the provided slug could be found.', - 'Organization has already been verified.': - 'Organization has already been verified.', - 'Organization name already in use, please choose another and try again.': - 'Organization name already in use, please choose another and try again.', - 'Organization name already in use. Please try again with a different name.': - 'Organization name already in use. Please try again with a different name.', - 'Ownership check error. Unable to request domain information.': - 'Ownership check error. Unable to request domain information.', - 'Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.': - 'Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.', - 'Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.': - 'Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.', - 'Passing both `first` and `last` to paginate the `DKIM` connection is not supported.': - 'Passing both `first` and `last` to paginate the `DKIM` connection is not supported.', - 'Passing both `first` and `last` to paginate the `DMARC` connection is not supported.': - 'Passing both `first` and `last` to paginate the `DMARC` connection is not supported.', - 'Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.': - 'Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.', - 'Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.': - 'Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.', - 'Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.': - 'Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.', - 'Passing both `first` and `last` to paginate the `Domain` connection is not supported.': - 'Passing both `first` and `last` to paginate the `Domain` connection is not supported.', - 'Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.': - 'Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.', - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.': - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.', - 'Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.': - 'Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.', - 'Passing both `first` and `last` to paginate the `Organization` connection is not supported.': - 'Passing both `first` and `last` to paginate the `Organization` connection is not supported.', - 'Passing both `first` and `last` to paginate the `SPF` connection is not supported.': - 'Passing both `first` and `last` to paginate the `SPF` connection is not supported.', - 'Passing both `first` and `last` to paginate the `SSL` connection is not supported.': - 'Passing both `first` and `last` to paginate the `SSL` connection is not supported.', - 'Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.': - 'Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.', - 'Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.': - 'Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.', - 'Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.': - 'Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.', - 'Password does not meet requirements.': - 'Password does not meet requirements.', - 'Password was successfully reset.': 'Password was successfully reset.', - 'Password was successfully updated.': 'Password was successfully updated.', - 'Passwords do not match.': 'Passwords do not match.', - 'Permission Denied: Could not retrieve specified organization.': - 'Permission Denied: Could not retrieve specified organization.', - 'Permission Denied: Please contact org owner to transfer ownership.': - 'Permission Denied: Please contact org owner to transfer ownership.', - 'Permission Denied: Please contact organization admin for help with removing domain.': - 'Permission Denied: Please contact organization admin for help with removing domain.', - 'Permission Denied: Please contact organization admin for help with removing organization.': - 'Permission Denied: Please contact organization admin for help with removing organization.', - 'Permission Denied: Please contact organization admin for help with removing users.': - 'Permission Denied: Please contact organization admin for help with removing users.', - 'Permission Denied: Please contact organization admin for help with updating organization.': - 'Permission Denied: Please contact organization admin for help with updating organization.', - 'Permission Denied: Please contact organization admin for help with updating user roles.': - 'Permission Denied: Please contact organization admin for help with updating user roles.', - 'Permission Denied: Please contact organization admin for help with user invitations.': - 'Permission Denied: Please contact organization admin for help with user invitations.', - 'Permission Denied: Please contact organization admin for help with user role changes.': - 'Permission Denied: Please contact organization admin for help with user role changes.', - 'Permission Denied: Please contact organization user for help with creating domain.': - 'Permission Denied: Please contact organization user for help with creating domain.', - 'Permission Denied: Please contact organization user for help with retrieving this domain.': - 'Permission Denied: Please contact organization user for help with retrieving this domain.', - 'Permission Denied: Please contact organization user for help with scanning this domain.': - 'Permission Denied: Please contact organization user for help with scanning this domain.', - 'Permission Denied: Please contact organization user for help with updating this domain.': - 'Permission Denied: Please contact organization user for help with updating this domain.', - 'Permission Denied: Please contact super admin for help with removing domain.': - 'Permission Denied: Please contact super admin for help with removing domain.', - 'Permission Denied: Please contact super admin for help with removing organization.': - 'Permission Denied: Please contact super admin for help with removing organization.', - 'Permission Denied: Please contact super admin for help with verifying this organization.': - 'Permission Denied: Please contact super admin for help with verifying this organization.', - 'Permission check error. Unable to request domain information.': - 'Permission check error. Unable to request domain information.', - 'Permission error, not an admin for this user.': - 'Permission error, not an admin for this user.', - "Permission error: Unable to close other user's account.": - "Permission error: Unable to close other user's account.", - 'Phone number has been successfully removed.': - 'Phone number has been successfully removed.', - 'Phone number has been successfully set, you will receive a verification text message shortly.': - 'Phone number has been successfully set, you will receive a verification text message shortly.', - 'Profile successfully updated.': 'Profile successfully updated.', - 'Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `Affiliation` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `DkimFailureTable` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `DmarcFailureTable` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `DmarcSummaries` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `Domain` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `FullPassTable` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `GuidanceTag` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `Organization` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `SpfFailureTable` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `VerifiedDomain` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting `', - ['amount'], - '` records on the `VerifiedOrganization` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting ', - ['amount'], - ' records on the `DKIMResults` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting ', - ['amount'], - ' records on the `DKIM` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting ', - ['amount'], - ' records on the `DMARC` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting ', - ['amount'], - ' records on the `HTTPS` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting ', - ['amount'], - ' records on the `SPF` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'Requesting ', - ['amount'], - ' records on the `SSL` connection exceeds the `', - ['argSet'], - '` limit of 100 records.', - ], - 'Successfully closed account.': 'Successfully closed account.', - 'Successfully dispatched one time scan.': - 'Successfully dispatched one time scan.', - 'Successfully email verified account, and set TFA send method to email.': - 'Successfully email verified account, and set TFA send method to email.', - 'Successfully invited user to organization, and sent notification email.': - 'Successfully invited user to organization, and sent notification email.', - 'Successfully left organization: {0}': [ - 'Successfully left organization: ', - ['0'], - ], - 'Successfully removed domain: {0} from {1}.': [ - 'Successfully removed domain: ', - ['0'], - ' from ', - ['1'], - '.', - ], - 'Successfully removed organization: {0}.': [ - 'Successfully removed organization: ', - ['0'], - '.', - ], - 'Successfully removed user from organization.': - 'Successfully removed user from organization.', - 'Successfully sent invitation to service, and organization email.': - 'Successfully sent invitation to service, and organization email.', - 'Successfully signed out.': 'Successfully signed out.', - 'Successfully transferred org: {0} ownership to user: {1}': [ - 'Successfully transferred org: ', - ['0'], - ' ownership to user: ', - ['1'], - ], - 'Successfully verified organization: {0}.': [ - 'Successfully verified organization: ', - ['0'], - '.', - ], - 'Successfully verified phone number, and set TFA send method to text.': - 'Successfully verified phone number, and set TFA send method to text.', - 'Token value incorrect, please sign in again.': - 'Token value incorrect, please sign in again.', - 'Too many failed login attempts, please reset your password, and try again.': - 'Too many failed login attempts, please reset your password, and try again.', - 'Two factor code is incorrect. Please try again.': - 'Two factor code is incorrect. Please try again.', - 'Two factor code length is incorrect. Please try again.': - 'Two factor code length is incorrect. Please try again.', - 'Unable leave organization. Please try again.': - 'Unable leave organization. Please try again.', - 'Unable to authenticate. Please try again.': - 'Unable to authenticate. Please try again.', - 'Unable to check permission. Please try again.': - 'Unable to check permission. Please try again.', - 'Unable to close account of an undefined user.': - 'Unable to close account of an undefined user.', - 'Unable to close account. Please try again.': - 'Unable to close account. Please try again.', - 'Unable to create domain in unknown organization.': - 'Unable to create domain in unknown organization.', - 'Unable to create domain, organization has already claimed it.': - 'Unable to create domain, organization has already claimed it.', - 'Unable to create domain. Please try again.': - 'Unable to create domain. Please try again.', - 'Unable to create organization. Please try again.': - 'Unable to create organization. Please try again.', - 'Unable to dispatch one time scan. Please try again.': - 'Unable to dispatch one time scan. Please try again.', - 'Unable to find Aggregate guidance tag(s). Please try again.': - 'Unable to find Aggregate guidance tag(s). Please try again.', - 'Unable to find DKIM guidance tag(s). Please try again.': - 'Unable to find DKIM guidance tag(s). Please try again.', - 'Unable to find DKIM result(s). Please try again.': - 'Unable to find DKIM result(s). Please try again.', - 'Unable to find DKIM scan(s). Please try again.': - 'Unable to find DKIM scan(s). Please try again.', - 'Unable to find DMARC guidance tag(s). Please try again.': - 'Unable to find DMARC guidance tag(s). Please try again.', - 'Unable to find DMARC scan(s). Please try again.': - 'Unable to find DMARC scan(s). Please try again.', - 'Unable to find DMARC summary data. Please try again.': - 'Unable to find DMARC summary data. Please try again.', - 'Unable to find HTTPS guidance tag(s). Please try again.': - 'Unable to find HTTPS guidance tag(s). Please try again.', - 'Unable to find HTTPS scan(s). Please try again.': - 'Unable to find HTTPS scan(s). Please try again.', - 'Unable to find SPF guidance tag(s). Please try again.': - 'Unable to find SPF guidance tag(s). Please try again.', - 'Unable to find SPF scan(s). Please try again.': - 'Unable to find SPF scan(s). Please try again.', - 'Unable to find SSL guidance tag(s). Please try again.': - 'Unable to find SSL guidance tag(s). Please try again.', - 'Unable to find SSL scan(s). Please try again.': - 'Unable to find SSL scan(s). Please try again.', - 'Unable to find the requested domain.': - 'Unable to find the requested domain.', - 'Unable to find user affiliation(s). Please try again.': - 'Unable to find user affiliation(s). Please try again.', - 'Unable to find verified organization(s). Please try again.': - 'Unable to find verified organization(s). Please try again.', - 'Unable to invite user to unknown organization.': - 'Unable to invite user to unknown organization.', - 'Unable to invite user. Please try again.': - 'Unable to invite user. Please try again.', - 'Unable to invite yourself to an org.': - 'Unable to invite yourself to an org.', - 'Unable to leave undefined organization.': - 'Unable to leave undefined organization.', - 'Unable to load Aggregate guidance tag(s). Please try again.': - 'Unable to load Aggregate guidance tag(s). Please try again.', - 'Unable to load DKIM failure data. Please try again.': - 'Unable to load DKIM failure data. Please try again.', - 'Unable to load DKIM guidance tag(s). Please try again.': - 'Unable to load DKIM guidance tag(s). Please try again.', - 'Unable to load DKIM result(s). Please try again.': - 'Unable to load DKIM result(s). Please try again.', - 'Unable to load DKIM scan(s). Please try again.': - 'Unable to load DKIM scan(s). Please try again.', - 'Unable to load DMARC failure data. Please try again.': - 'Unable to load DMARC failure data. Please try again.', - 'Unable to load DMARC guidance tag(s). Please try again.': - 'Unable to load DMARC guidance tag(s). Please try again.', - 'Unable to load DMARC scan(s). Please try again.': - 'Unable to load DMARC scan(s). Please try again.', - 'Unable to load DMARC summary data. Please try again.': - 'Unable to load DMARC summary data. Please try again.', - 'Unable to load HTTPS guidance tag(s). Please try again.': - 'Unable to load HTTPS guidance tag(s). Please try again.', - 'Unable to load HTTPS scan(s). Please try again.': - 'Unable to load HTTPS scan(s). Please try again.', - 'Unable to load SPF failure data. Please try again.': - 'Unable to load SPF failure data. Please try again.', - 'Unable to load SPF guidance tag(s). Please try again.': - 'Unable to load SPF guidance tag(s). Please try again.', - 'Unable to load SPF scan(s). Please try again.': - 'Unable to load SPF scan(s). Please try again.', - 'Unable to load SSL guidance tag(s). Please try again.': - 'Unable to load SSL guidance tag(s). Please try again.', - 'Unable to load SSL scan(s). Please try again.': - 'Unable to load SSL scan(s). Please try again.', - 'Unable to load affiliation information. Please try again.': - 'Unable to load affiliation information. Please try again.', - 'Unable to load affiliation(s). Please try again.': - 'Unable to load affiliation(s). Please try again.', - 'Unable to load domain(s). Please try again.': - 'Unable to load domain(s). Please try again.', - 'Unable to load domain. Please try again.': - 'Unable to load domain. Please try again.', - 'Unable to load full pass data. Please try again.': - 'Unable to load full pass data. Please try again.', - 'Unable to load mail summary. Please try again.': - 'Unable to load mail summary. Please try again.', - 'Unable to load organization(s). Please try again.': - 'Unable to load organization(s). Please try again.', - 'Unable to load owner information. Please try again.': - 'Unable to load owner information. Please try again.', - 'Unable to load summary. Please try again.': - 'Unable to load summary. Please try again.', - 'Unable to load user(s). Please try again.': - 'Unable to load user(s). Please try again.', - 'Unable to load verified domain(s). Please try again.': - 'Unable to load verified domain(s). Please try again.', - 'Unable to load verified organization(s). Please try again.': - 'Unable to load verified organization(s). Please try again.', - 'Unable to load web summary. Please try again.': - 'Unable to load web summary. Please try again.', - 'Unable to query affiliation(s). Please try again.': - 'Unable to query affiliation(s). Please try again.', - 'Unable to query domain(s). Please try again.': - 'Unable to query domain(s). Please try again.', - 'Unable to refresh tokens, please sign in.': - 'Unable to refresh tokens, please sign in.', - 'Unable to remove a user that already does not belong to this organization.': - 'Unable to remove a user that already does not belong to this organization.', - 'Unable to remove domain from unknown organization.': - 'Unable to remove domain from unknown organization.', - 'Unable to remove domain. Please try again.': - 'Unable to remove domain. Please try again.', - 'Unable to remove organization. Please try again.': - 'Unable to remove organization. Please try again.', - 'Unable to remove phone number. Please try again.': - 'Unable to remove phone number. Please try again.', - 'Unable to remove unknown domain.': 'Unable to remove unknown domain.', - 'Unable to remove unknown organization.': - 'Unable to remove unknown organization.', - 'Unable to remove unknown user from organization.': - 'Unable to remove unknown user from organization.', - 'Unable to remove user from organization.': - 'Unable to remove user from organization.', - 'Unable to remove user from this organization. Please try again.': - 'Unable to remove user from this organization. Please try again.', - 'Unable to remove user from unknown organization.': - 'Unable to remove user from unknown organization.', - 'Unable to request a one time scan on an unknown domain.': - 'Unable to request a one time scan on an unknown domain.', - 'Unable to reset password. Please request a new email.': - 'Unable to reset password. Please request a new email.', - 'Unable to reset password. Please try again.': - 'Unable to reset password. Please try again.', - 'Unable to retrieve DMARC report information for: {domain}': [ - 'Unable to retrieve DMARC report information for: ', - ['domain'], - ], - 'Unable to select DMARC report(s) for this period and year.': - 'Unable to select DMARC report(s) for this period and year.', - 'Unable to send authentication email. Please try again.': - 'Unable to send authentication email. Please try again.', - 'Unable to send authentication text message. Please try again.': - 'Unable to send authentication text message. Please try again.', - 'Unable to send org invite email. Please try again.': - 'Unable to send org invite email. Please try again.', - 'Unable to send password reset email. Please try again.': - 'Unable to send password reset email. Please try again.', - 'Unable to send two factor authentication message. Please try again.': - 'Unable to send two factor authentication message. Please try again.', - 'Unable to send verification email. Please try again.': - 'Unable to send verification email. Please try again.', - 'Unable to set phone number, please try again.': - 'Unable to set phone number, please try again.', - 'Unable to sign in, please try again.': - 'Unable to sign in, please try again.', - 'Unable to sign up, please contact org admin for a new invite.': - 'Unable to sign up, please contact org admin for a new invite.', - 'Unable to sign up. Please try again.': - 'Unable to sign up. Please try again.', - 'Unable to transfer organization ownership. Please try again.': - 'Unable to transfer organization ownership. Please try again.', - 'Unable to transfer ownership of a verified organization.': - 'Unable to transfer ownership of a verified organization.', - 'Unable to transfer ownership of an org to an undefined user.': - 'Unable to transfer ownership of an org to an undefined user.', - 'Unable to transfer ownership of undefined organization.': - 'Unable to transfer ownership of undefined organization.', - 'Unable to transfer ownership to a user outside the org. Please invite the user and try again.': - 'Unable to transfer ownership to a user outside the org. Please invite the user and try again.', - 'Unable to two factor authenticate. Please try again.': - 'Unable to two factor authenticate. Please try again.', - 'Unable to update domain in an unknown org.': - 'Unable to update domain in an unknown org.', - 'Unable to update domain that does not belong to the given organization.': - 'Unable to update domain that does not belong to the given organization.', - 'Unable to update domain. Please try again.': - 'Unable to update domain. Please try again.', - 'Unable to update organization. Please try again.': - 'Unable to update organization. Please try again.', - 'Unable to update password, current password does not match. Please try again.': - 'Unable to update password, current password does not match. Please try again.', - 'Unable to update password, new passwords do not match. Please try again.': - 'Unable to update password, new passwords do not match. Please try again.', - 'Unable to update password, passwords do not match requirements. Please try again.': - 'Unable to update password, passwords do not match requirements. Please try again.', - 'Unable to update password. Please try again.': - 'Unable to update password. Please try again.', - 'Unable to update profile. Please try again.': - 'Unable to update profile. Please try again.', - 'Unable to update role: organization unknown.': - 'Unable to update role: organization unknown.', - 'Unable to update role: user does not belong to organization.': - 'Unable to update role: user does not belong to organization.', - 'Unable to update role: user unknown.': - 'Unable to update role: user unknown.', - 'Unable to update unknown domain.': 'Unable to update unknown domain.', - 'Unable to update unknown organization.': - 'Unable to update unknown organization.', - "Unable to update user's role. Please try again.": - "Unable to update user's role. Please try again.", - 'Unable to update your own role.': 'Unable to update your own role.', - 'Unable to verify account. Please request a new email.': - 'Unable to verify account. Please request a new email.', - 'Unable to verify account. Please try again.': - 'Unable to verify account. Please try again.', - 'Unable to verify if user is a super admin, please try again.': - 'Unable to verify if user is a super admin, please try again.', - 'Unable to verify if user is an admin, please try again.': - 'Unable to verify if user is an admin, please try again.', - 'Unable to verify organization. Please try again.': - 'Unable to verify organization. Please try again.', - 'Unable to verify unknown organization.': - 'Unable to verify unknown organization.', - 'User could not be queried.': 'User could not be queried.', - 'User role was updated successfully.': - 'User role was updated successfully.', - 'Username not available, please try another.': - 'Username not available, please try another.', - 'Verification error. Please verify your account via email to access content.': - 'Verification error. Please verify your account via email to access content.', - 'You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.': - 'You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.', - 'You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.': - 'You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.', - 'You must provide a `first` or `last` value to properly paginate the `DKIM` connection.': - 'You must provide a `first` or `last` value to properly paginate the `DKIM` connection.', - 'You must provide a `first` or `last` value to properly paginate the `DMARC` connection.': - 'You must provide a `first` or `last` value to properly paginate the `DMARC` connection.', - 'You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.': - 'You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.', - 'You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.': - 'You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.', - 'You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.': - 'You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.', - 'You must provide a `first` or `last` value to properly paginate the `Domain` connection.': - 'You must provide a `first` or `last` value to properly paginate the `Domain` connection.', - 'You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.': - 'You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.', - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.': - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.', - 'You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.': - 'You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.', - 'You must provide a `first` or `last` value to properly paginate the `Organization` connection.': - 'You must provide a `first` or `last` value to properly paginate the `Organization` connection.', - 'You must provide a `first` or `last` value to properly paginate the `SPF` connection.': - 'You must provide a `first` or `last` value to properly paginate the `SPF` connection.', - 'You must provide a `first` or `last` value to properly paginate the `SSL` connection.': - 'You must provide a `first` or `last` value to properly paginate the `SSL` connection.', - 'You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.': - 'You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.', - 'You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.': - 'You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.', - 'You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.': - 'You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.', - 'You must provide a `period` value to access the `DmarcSummaries` connection.': - 'You must provide a `period` value to access the `DmarcSummaries` connection.', - 'You must provide a `year` value to access the `DmarcSummaries` connection.': - 'You must provide a `year` value to access the `DmarcSummaries` connection.', - '`{argSet}` must be of type `number` not `{typeSet}`.': [ - '`', - ['argSet'], - '` must be of type `number` not `', - ['typeSet'], - '`.', - ], - '`{argSet}` on the `Affiliation` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `Affiliation` connection cannot be less than zero.', - ], - '`{argSet}` on the `DKIMResults` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `DKIMResults` connection cannot be less than zero.', - ], - '`{argSet}` on the `DKIM` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `DKIM` connection cannot be less than zero.', - ], - '`{argSet}` on the `DMARC` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `DMARC` connection cannot be less than zero.', - ], - '`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` on the `DkimFailureTable` connection cannot be less than zero.', - ], - '`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` on the `DmarcFailureTable` connection cannot be less than zero.', - ], - '`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `DmarcSummaries` connection cannot be less than zero.', - ], - '`{argSet}` on the `Domain` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `Domain` connection cannot be less than zero.', - ], - '`{argSet}` on the `FullPassTable` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `FullPassTable` connection cannot be less than zero.', - ], - '`{argSet}` on the `GuidanceTag` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `GuidanceTag` connection cannot be less than zero.', - ], - '`{argSet}` on the `HTTPS` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `HTTPS` connection cannot be less than zero.', - ], - '`{argSet}` on the `Organization` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `Organization` connection cannot be less than zero.', - ], - '`{argSet}` on the `SPF` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `SPF` connection cannot be less than zero.', - ], - '`{argSet}` on the `SSL` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `SSL` connection cannot be less than zero.', - ], - '`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` on the `SpfFailureTable` connection cannot be less than zero.', - ], - '`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` on the `VerifiedDomain` connection cannot be less than zero.', - ], - '`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` on the `VerifiedOrganization` connection cannot be less than zero.', - ], - }, -} diff --git a/api-js/src/locale/en/messages.po b/api-js/src/locale/en/messages.po deleted file mode 100644 index fd48157c16..0000000000 --- a/api-js/src/locale/en/messages.po +++ /dev/null @@ -1,1275 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language: \n" -"Language-Team: \n" -"Content-Type: \n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#: src/auth/check-permission.js:20 -#: src/auth/check-permission.js:48 -#: src/auth/user-required.js:10 -#: src/auth/user-required.js:21 -#: src/auth/user-required.js:28 -msgid "Authentication error. Please sign in." -msgstr "Authentication error. Please sign in." - -#: src/organization/objects/organization.js:142 -msgid "Cannot query affiliations on organization without admin permission or higher." -msgstr "Cannot query affiliations on organization without admin permission or higher." - -#: src/user/mutations/sign-up.js:116 -msgid "Email already in use." -msgstr "Email already in use." - -#: src/user/mutations/send-password-reset.js:62 -msgid "If an account with this username is found, a password reset link will be found in your inbox." -msgstr "If an account with this username is found, a password reset link will be found in your inbox." - -#: src/user/mutations/send-email-verification.js:61 -msgid "If an account with this username is found, an email verification link will be found in your inbox." -msgstr "If an account with this username is found, an email verification link will be found in your inbox." - -#: src/user/mutations/authenticate.js:177 -msgid "Incorrect TFA code. Please sign in again." -msgstr "Incorrect TFA code. Please sign in again." - -#: src/user/mutations/reset-password.js:66 -msgid "Incorrect token value. Please request a new email." -msgstr "Incorrect token value. Please request a new email." - -#: src/user/mutations/sign-in.js:70 -#: src/user/mutations/sign-in.js:295 -msgid "Incorrect username or password. Please try again." -msgstr "Incorrect username or password. Please try again." - -#: src/auth/verify-jwt.js:14 -msgid "Invalid token, please sign in." -msgstr "Invalid token, please sign in." - -#: src/user/mutations/reset-password.js:108 -msgid "New passwords do not match." -msgstr "New passwords do not match." - -#: src/organization/queries/find-organization-by-slug.js:42 -msgid "No organization with the provided slug could be found." -msgstr "No organization with the provided slug could be found." - -#: src/verified-domains/queries/find-verified-domain-by-domain.js:33 -msgid "No verified domain with the provided domain could be found." -msgstr "No verified domain with the provided domain could be found." - -#: src/verified-organizations/queries/find-verified-organization-by-slug.js:31 -msgid "No verified organization with the provided slug could be found." -msgstr "No verified organization with the provided slug could be found." - -#: src/organization/mutations/verify-organization.js:82 -msgid "Organization has already been verified." -msgstr "Organization has already been verified." - -#: src/organization/mutations/update-organization.js:188 -msgid "Organization name already in use, please choose another and try again." -msgstr "Organization name already in use, please choose another and try again." - -#: src/organization/mutations/create-organization.js:138 -msgid "Organization name already in use. Please try again with a different name." -msgstr "Organization name already in use. Please try again with a different name." - -#: src/auth/check-domain-ownership.js:30 -#: src/auth/check-domain-ownership.js:42 -#: src/auth/check-domain-ownership.js:68 -#: src/auth/check-domain-ownership.js:79 -msgid "Ownership check error. Unable to request domain information." -msgstr "Ownership check error. Unable to request domain information." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:80 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:170 -msgid "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:98 -msgid "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:123 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:159 -msgid "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:147 -msgid "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:193 -msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:147 -#: src/domain/loaders/load-domain-connections-by-user-id.js:156 -msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `Domain` connection is not supported." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:94 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:98 -msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:156 -msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:187 -#: src/organization/loaders/load-organization-connections-by-user-id.js:186 -msgid "Passing both `first` and `last` to paginate the `Organization` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `Organization` connection is not supported." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:142 -msgid "Passing both `first` and `last` to paginate the `SPF` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `SPF` connection is not supported." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:180 -msgid "Passing both `first` and `last` to paginate the `SSL` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `SSL` connection is not supported." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:126 -#: src/verified-domains/loaders/load-verified-domain-connections.js:126 -msgid "Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:176 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:174 -msgid "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." -msgstr "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." - -#: src/user/mutations/reset-password.js:120 -#: src/user/mutations/sign-up.js:90 -msgid "Password does not meet requirements." -msgstr "Password does not meet requirements." - -#: src/user/mutations/reset-password.js:168 -msgid "Password was successfully reset." -msgstr "Password was successfully reset." - -#: src/user/mutations/update-user-password.js:134 -msgid "Password was successfully updated." -msgstr "Password was successfully updated." - -#: src/user/mutations/sign-up.js:102 -msgid "Passwords do not match." -msgstr "Passwords do not match." - -#: src/organization/queries/find-organization-by-slug.js:53 -msgid "Permission Denied: Could not retrieve specified organization." -msgstr "Permission Denied: Could not retrieve specified organization." - -#: src/affiliation/mutations/transfer-org-ownership.js:95 -msgid "Permission Denied: Please contact org owner to transfer ownership." -msgstr "Permission Denied: Please contact org owner to transfer ownership." - -#: src/domain/mutations/remove-domain.js:109 -msgid "Permission Denied: Please contact organization admin for help with removing domain." -msgstr "Permission Denied: Please contact organization admin for help with removing domain." - -#: src/organization/mutations/remove-organization.js:71 -msgid "Permission Denied: Please contact organization admin for help with removing organization." -msgstr "Permission Denied: Please contact organization admin for help with removing organization." - -#: src/affiliation/mutations/remove-user-from-org.js:214 -msgid "Permission Denied: Please contact organization admin for help with removing users." -msgstr "Permission Denied: Please contact organization admin for help with removing users." - -#: src/organization/mutations/update-organization.js:156 -msgid "Permission Denied: Please contact organization admin for help with updating organization." -msgstr "Permission Denied: Please contact organization admin for help with updating organization." - -#: src/affiliation/mutations/update-user-role.js:190 -#: src/affiliation/mutations/update-user-role.js:213 -#: src/affiliation/mutations/update-user-role.js:231 -msgid "Permission Denied: Please contact organization admin for help with updating user roles." -msgstr "Permission Denied: Please contact organization admin for help with updating user roles." - -#: src/affiliation/mutations/invite-user-to-org.js:106 -msgid "Permission Denied: Please contact organization admin for help with user invitations." -msgstr "Permission Denied: Please contact organization admin for help with user invitations." - -#: src/affiliation/mutations/update-user-role.js:112 -msgid "Permission Denied: Please contact organization admin for help with user role changes." -msgstr "Permission Denied: Please contact organization admin for help with user role changes." - -#: src/domain/mutations/create-domain.js:95 -msgid "Permission Denied: Please contact organization user for help with creating domain." -msgstr "Permission Denied: Please contact organization user for help with creating domain." - -#: src/domain/queries/find-domain-by-domain.js:48 -msgid "Permission Denied: Please contact organization user for help with retrieving this domain." -msgstr "Permission Denied: Please contact organization user for help with retrieving this domain." - -#: src/domain/mutations/request-scan.js:70 -msgid "Permission Denied: Please contact organization user for help with scanning this domain." -msgstr "Permission Denied: Please contact organization user for help with scanning this domain." - -#: src/domain/mutations/update-domain.js:112 -msgid "Permission Denied: Please contact organization user for help with updating this domain." -msgstr "Permission Denied: Please contact organization user for help with updating this domain." - -#: src/domain/mutations/remove-domain.js:96 -msgid "Permission Denied: Please contact super admin for help with removing domain." -msgstr "Permission Denied: Please contact super admin for help with removing domain." - -#: src/organization/mutations/remove-organization.js:85 -msgid "Permission Denied: Please contact super admin for help with removing organization." -msgstr "Permission Denied: Please contact super admin for help with removing organization." - -#: src/organization/mutations/verify-organization.js:69 -msgid "Permission Denied: Please contact super admin for help with verifying this organization." -msgstr "Permission Denied: Please contact super admin for help with verifying this organization." - -#: src/auth/check-domain-permission.js:24 -#: src/auth/check-domain-permission.js:48 -#: src/auth/check-domain-permission.js:61 -msgid "Permission check error. Unable to request domain information." -msgstr "Permission check error. Unable to request domain information." - -#: src/auth/check-user-is-admin-for-user.js:20 -#: src/auth/check-user-is-admin-for-user.js:30 -#: src/auth/check-user-is-admin-for-user.js:63 -#: src/auth/check-user-is-admin-for-user.js:75 -msgid "Permission error, not an admin for this user." -msgstr "Permission error, not an admin for this user." - -#: src/user/mutations/close-account.js:54 -msgid "Permission error: Unable to close other user's account." -msgstr "Permission error: Unable to close other user's account." - -#: src/user/mutations/remove-phone-number.js:81 -msgid "Phone number has been successfully removed." -msgstr "Phone number has been successfully removed." - -#: src/user/mutations/set-phone-number.js:131 -msgid "Phone number has been successfully set, you will receive a verification text message shortly." -msgstr "Phone number has been successfully set, you will receive a verification text message shortly." - -#: src/user/mutations/update-user-profile.js:160 -msgid "Profile successfully updated." -msgstr "Profile successfully updated." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:103 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:193 -msgid "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:216 -msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:170 -#: src/domain/loaders/load-domain-connections-by-user-id.js:179 -msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:117 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:121 -msgid "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:210 -#: src/organization/loaders/load-organization-connections-by-user-id.js:209 -msgid "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:149 -#: src/verified-domains/loaders/load-verified-domain-connections.js:149 -msgid "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:199 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:197 -msgid "Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:121 -msgid "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:146 -msgid "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:171 -msgid "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:179 -msgid "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:165 -msgid "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:203 -msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." -msgstr "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." - -#: src/user/mutations/close-account.js:508 -msgid "Successfully closed account." -msgstr "Successfully closed account." - -#: src/domain/mutations/request-scan.js:131 -msgid "Successfully dispatched one time scan." -msgstr "Successfully dispatched one time scan." - -#: src/user/mutations/verify-account.js:118 -msgid "Successfully email verified account, and set TFA send method to email." -msgstr "Successfully email verified account, and set TFA send method to email." - -#: src/affiliation/mutations/invite-user-to-org.js:195 -msgid "Successfully invited user to organization, and sent notification email." -msgstr "Successfully invited user to organization, and sent notification email." - -#: src/affiliation/mutations/leave-organization.js:444 -msgid "Successfully left organization: {0}" -msgstr "Successfully left organization: {0}" - -#: src/domain/mutations/remove-domain.js:402 -msgid "Successfully removed domain: {0} from {1}." -msgstr "Successfully removed domain: {0} from {1}." - -#: src/organization/mutations/remove-organization.js:471 -msgid "Successfully removed organization: {0}." -msgstr "Successfully removed organization: {0}." - -#: src/affiliation/mutations/remove-user-from-org.js:200 -msgid "Successfully removed user from organization." -msgstr "Successfully removed user from organization." - -#: src/affiliation/mutations/invite-user-to-org.js:137 -msgid "Successfully sent invitation to service, and organization email." -msgstr "Successfully sent invitation to service, and organization email." - -#: src/user/mutations/sign-out.js:25 -msgid "Successfully signed out." -msgstr "Successfully signed out." - -#: src/affiliation/mutations/transfer-org-ownership.js:224 -msgid "Successfully transferred org: {0} ownership to user: {1}" -msgstr "Successfully transferred org: {0} ownership to user: {1}" - -#: src/organization/mutations/verify-organization.js:157 -msgid "Successfully verified organization: {0}." -msgstr "Successfully verified organization: {0}." - -#: src/user/mutations/verify-phone-number.js:118 -msgid "Successfully verified phone number, and set TFA send method to text." -msgstr "Successfully verified phone number, and set TFA send method to text." - -#: src/user/mutations/authenticate.js:62 -msgid "Token value incorrect, please sign in again." -msgstr "Token value incorrect, please sign in again." - -#: src/user/mutations/sign-in.js:84 -msgid "Too many failed login attempts, please reset your password, and try again." -msgstr "Too many failed login attempts, please reset your password, and try again." - -#: src/user/mutations/verify-phone-number.js:63 -msgid "Two factor code is incorrect. Please try again." -msgstr "Two factor code is incorrect. Please try again." - -#: src/user/mutations/verify-phone-number.js:50 -msgid "Two factor code length is incorrect. Please try again." -msgstr "Two factor code length is incorrect. Please try again." - -#: src/affiliation/mutations/leave-organization.js:80 -#: src/affiliation/mutations/leave-organization.js:90 -#: src/affiliation/mutations/leave-organization.js:121 -#: src/affiliation/mutations/leave-organization.js:138 -#: src/affiliation/mutations/leave-organization.js:169 -#: src/affiliation/mutations/leave-organization.js:179 -#: src/affiliation/mutations/leave-organization.js:217 -#: src/affiliation/mutations/leave-organization.js:339 -#: src/affiliation/mutations/leave-organization.js:372 -#: src/affiliation/mutations/leave-organization.js:409 -#: src/affiliation/mutations/leave-organization.js:427 -#: src/affiliation/mutations/leave-organization.js:437 -msgid "Unable leave organization. Please try again." -msgstr "Unable leave organization. Please try again." - -#: src/user/mutations/authenticate.js:77 -#: src/user/mutations/authenticate.js:125 -#: src/user/mutations/authenticate.js:134 -msgid "Unable to authenticate. Please try again." -msgstr "Unable to authenticate. Please try again." - -#: src/auth/check-permission.js:30 -#: src/auth/check-permission.js:58 -#: src/auth/check-super-admin.js:20 -#: src/auth/check-super-admin.js:30 -msgid "Unable to check permission. Please try again." -msgstr "Unable to check permission. Please try again." - -#: src/user/mutations/close-account.js:67 -msgid "Unable to close account of an undefined user." -msgstr "Unable to close account of an undefined user." - -#: src/user/mutations/close-account.js:88 -#: src/user/mutations/close-account.js:98 -#: src/user/mutations/close-account.js:123 -#: src/user/mutations/close-account.js:133 -#: src/user/mutations/close-account.js:164 -#: src/user/mutations/close-account.js:179 -#: src/user/mutations/close-account.js:208 -#: src/user/mutations/close-account.js:218 -#: src/user/mutations/close-account.js:254 -#: src/user/mutations/close-account.js:366 -#: src/user/mutations/close-account.js:397 -#: src/user/mutations/close-account.js:422 -#: src/user/mutations/close-account.js:458 -#: src/user/mutations/close-account.js:475 -#: src/user/mutations/close-account.js:490 -#: src/user/mutations/close-account.js:499 -msgid "Unable to close account. Please try again." -msgstr "Unable to close account. Please try again." - -#: src/domain/mutations/create-domain.js:75 -msgid "Unable to create domain in unknown organization." -msgstr "Unable to create domain in unknown organization." - -#: src/domain/mutations/create-domain.js:151 -msgid "Unable to create domain, organization has already claimed it." -msgstr "Unable to create domain, organization has already claimed it." - -#: src/domain/mutations/create-domain.js:130 -#: src/domain/mutations/create-domain.js:140 -#: src/domain/mutations/create-domain.js:189 -#: src/domain/mutations/create-domain.js:199 -#: src/domain/mutations/create-domain.js:217 -#: src/domain/mutations/create-domain.js:247 -#: src/domain/mutations/create-domain.js:265 -#: src/domain/mutations/create-domain.js:275 -msgid "Unable to create domain. Please try again." -msgstr "Unable to create domain. Please try again." - -#: src/organization/mutations/create-organization.js:206 -#: src/organization/mutations/create-organization.js:229 -#: src/organization/mutations/create-organization.js:240 -msgid "Unable to create organization. Please try again." -msgstr "Unable to create organization. Please try again." - -#: src/domain/mutations/request-scan.js:95 -#: src/domain/mutations/request-scan.js:109 -#: src/domain/mutations/request-scan.js:123 -msgid "Unable to dispatch one time scan. Please try again." -msgstr "Unable to dispatch one time scan. Please try again." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:48 -msgid "Unable to find Aggregate guidance tag(s). Please try again." -msgstr "Unable to find Aggregate guidance tag(s). Please try again." - -#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:48 -msgid "Unable to find DKIM guidance tag(s). Please try again." -msgstr "Unable to find DKIM guidance tag(s). Please try again." - -#: src/email-scan/loaders/load-dkim-result-by-key.js:20 -#: src/email-scan/loaders/load-dkim-result-by-key.js:34 -msgid "Unable to find DKIM result(s). Please try again." -msgstr "Unable to find DKIM result(s). Please try again." - -#: src/email-scan/loaders/load-dkim-by-key.js:19 -#: src/email-scan/loaders/load-dkim-by-key.js:31 -msgid "Unable to find DKIM scan(s). Please try again." -msgstr "Unable to find DKIM scan(s). Please try again." - -#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:48 -msgid "Unable to find DMARC guidance tag(s). Please try again." -msgstr "Unable to find DMARC guidance tag(s). Please try again." - -#: src/email-scan/loaders/load-dmarc-by-key.js:20 -#: src/email-scan/loaders/load-dmarc-by-key.js:34 -msgid "Unable to find DMARC scan(s). Please try again." -msgstr "Unable to find DMARC scan(s). Please try again." - -#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:37 -#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:51 -msgid "Unable to find DMARC summary data. Please try again." -msgstr "Unable to find DMARC summary data. Please try again." - -#: src/guidance-tag/loaders/load-https-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-https-guidance-tags.js:48 -msgid "Unable to find HTTPS guidance tag(s). Please try again." -msgstr "Unable to find HTTPS guidance tag(s). Please try again." - -#: src/web-scan/loaders/load-https-by-key.js:19 -msgid "Unable to find HTTPS scan(s). Please try again." -msgstr "Unable to find HTTPS scan(s). Please try again." - -#: src/guidance-tag/loaders/load-spf-guidance-tags.js:29 -#: src/guidance-tag/loaders/load-spf-guidance-tags.js:43 -msgid "Unable to find SPF guidance tag(s). Please try again." -msgstr "Unable to find SPF guidance tag(s). Please try again." - -#: src/email-scan/loaders/load-spf-by-key.js:19 -#: src/email-scan/loaders/load-spf-by-key.js:31 -msgid "Unable to find SPF scan(s). Please try again." -msgstr "Unable to find SPF scan(s). Please try again." - -#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:29 -#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:43 -msgid "Unable to find SSL guidance tag(s). Please try again." -msgstr "Unable to find SSL guidance tag(s). Please try again." - -#: src/web-scan/loaders/load-ssl-by-key.js:18 -#: src/web-scan/loaders/load-ssl-by-key.js:30 -msgid "Unable to find SSL scan(s). Please try again." -msgstr "Unable to find SSL scan(s). Please try again." - -#: src/domain/queries/find-domain-by-domain.js:38 -msgid "Unable to find the requested domain." -msgstr "Unable to find the requested domain." - -#: src/affiliation/loaders/load-affiliation-by-key.js:22 -#: src/affiliation/loaders/load-affiliation-by-key.js:36 -msgid "Unable to find user affiliation(s). Please try again." -msgstr "Unable to find user affiliation(s). Please try again." - -#: src/verified-organizations/loaders/load-verified-organization-by-key.js:32 -#: src/verified-organizations/loaders/load-verified-organization-by-key.js:44 -#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:32 -#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:44 -msgid "Unable to find verified organization(s). Please try again." -msgstr "Unable to find verified organization(s). Please try again." - -#: src/affiliation/mutations/invite-user-to-org.js:87 -msgid "Unable to invite user to unknown organization." -msgstr "Unable to invite user to unknown organization." - -#: src/affiliation/mutations/invite-user-to-org.js:170 -#: src/affiliation/mutations/invite-user-to-org.js:185 -msgid "Unable to invite user. Please try again." -msgstr "Unable to invite user. Please try again." - -#: src/affiliation/mutations/invite-user-to-org.js:73 -msgid "Unable to invite yourself to an org." -msgstr "Unable to invite yourself to an org." - -#: src/affiliation/mutations/leave-organization.js:51 -msgid "Unable to leave undefined organization." -msgstr "Unable to leave undefined organization." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:254 -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:266 -msgid "Unable to load Aggregate guidance tag(s). Please try again." -msgstr "Unable to load Aggregate guidance tag(s). Please try again." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:141 -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:153 -msgid "Unable to load DKIM failure data. Please try again." -msgstr "Unable to load DKIM failure data. Please try again." - -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:272 -msgid "Unable to load DKIM guidance tag(s). Please try again." -msgstr "Unable to load DKIM guidance tag(s). Please try again." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:258 -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:270 -msgid "Unable to load DKIM result(s). Please try again." -msgstr "Unable to load DKIM result(s). Please try again." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:279 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:289 -msgid "Unable to load DKIM scan(s). Please try again." -msgstr "Unable to load DKIM scan(s). Please try again." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:153 -msgid "Unable to load DMARC failure data. Please try again." -msgstr "Unable to load DMARC failure data. Please try again." - -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:272 -msgid "Unable to load DMARC guidance tag(s). Please try again." -msgstr "Unable to load DMARC guidance tag(s). Please try again." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:319 -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:331 -msgid "Unable to load DMARC scan(s). Please try again." -msgstr "Unable to load DMARC scan(s). Please try again." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:472 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:484 -#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:20 -#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:32 -#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:20 -#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:32 -msgid "Unable to load DMARC summary data. Please try again." -msgstr "Unable to load DMARC summary data. Please try again." - -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:272 -msgid "Unable to load HTTPS guidance tag(s). Please try again." -msgstr "Unable to load HTTPS guidance tag(s). Please try again." - -#: src/web-scan/loaders/load-https-by-key.js:33 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:333 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:345 -msgid "Unable to load HTTPS scan(s). Please try again." -msgstr "Unable to load HTTPS scan(s). Please try again." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:140 -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:152 -msgid "Unable to load SPF failure data. Please try again." -msgstr "Unable to load SPF failure data. Please try again." - -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:272 -msgid "Unable to load SPF guidance tag(s). Please try again." -msgstr "Unable to load SPF guidance tag(s). Please try again." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:306 -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:316 -msgid "Unable to load SPF scan(s). Please try again." -msgstr "Unable to load SPF scan(s). Please try again." - -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 -msgid "Unable to load SSL guidance tag(s). Please try again." -msgstr "Unable to load SSL guidance tag(s). Please try again." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:380 -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:390 -msgid "Unable to load SSL scan(s). Please try again." -msgstr "Unable to load SSL scan(s). Please try again." - -#: src/auth/check-user-belongs-to-org.js:22 -msgid "Unable to load affiliation information. Please try again." -msgstr "Unable to load affiliation information. Please try again." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:266 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:449 -msgid "Unable to load affiliation(s). Please try again." -msgstr "Unable to load affiliation(s). Please try again." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:364 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:374 -#: src/domain/loaders/load-domain-connections-by-user-id.js:394 -msgid "Unable to load domain(s). Please try again." -msgstr "Unable to load domain(s). Please try again." - -#: src/domain/loaders/load-domain-by-domain.js:19 -#: src/domain/loaders/load-domain-by-domain.js:31 -#: src/domain/loaders/load-domain-by-key.js:19 -#: src/domain/loaders/load-domain-by-key.js:31 -msgid "Unable to load domain. Please try again." -msgstr "Unable to load domain. Please try again." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:140 -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:152 -msgid "Unable to load full pass data. Please try again." -msgstr "Unable to load full pass data. Please try again." - -#: src/summaries/queries/mail-summary.js:12 -msgid "Unable to load mail summary. Please try again." -msgstr "Unable to load mail summary. Please try again." - -#: src/organization/loaders/load-organization-by-key.js:33 -#: src/organization/loaders/load-organization-by-key.js:47 -#: src/organization/loaders/load-organization-by-slug.js:36 -#: src/organization/loaders/load-organization-by-slug.js:51 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:513 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:525 -#: src/organization/loaders/load-organization-connections-by-user-id.js:504 -#: src/organization/loaders/load-organization-connections-by-user-id.js:516 -msgid "Unable to load organization(s). Please try again." -msgstr "Unable to load organization(s). Please try again." - -#: src/auth/check-org-owner.js:22 -#: src/auth/check-org-owner.js:34 -msgid "Unable to load owner information. Please try again." -msgstr "Unable to load owner information. Please try again." - -#: src/summaries/loaders/load-chart-summary-by-key.js:19 -#: src/summaries/loaders/load-chart-summary-by-key.js:31 -msgid "Unable to load summary. Please try again." -msgstr "Unable to load summary. Please try again." - -#: src/user/loaders/load-user-by-key.js:19 -#: src/user/loaders/load-user-by-key.js:31 -#: src/user/loaders/load-user-by-username.js:19 -#: src/user/loaders/load-user-by-username.js:31 -msgid "Unable to load user(s). Please try again." -msgstr "Unable to load user(s). Please try again." - -#: src/verified-domains/loaders/load-verified-domain-by-domain.js:24 -#: src/verified-domains/loaders/load-verified-domain-by-domain.js:38 -#: src/verified-domains/loaders/load-verified-domain-by-key.js:24 -#: src/verified-domains/loaders/load-verified-domain-by-key.js:38 -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:306 -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:318 -#: src/verified-domains/loaders/load-verified-domain-connections.js:312 -#: src/verified-domains/loaders/load-verified-domain-connections.js:324 -msgid "Unable to load verified domain(s). Please try again." -msgstr "Unable to load verified domain(s). Please try again." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:423 -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:435 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:422 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:434 -msgid "Unable to load verified organization(s). Please try again." -msgstr "Unable to load verified organization(s). Please try again." - -#: src/summaries/queries/web-summary.js:13 -msgid "Unable to load web summary. Please try again." -msgstr "Unable to load web summary. Please try again." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:254 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:437 -msgid "Unable to query affiliation(s). Please try again." -msgstr "Unable to query affiliation(s). Please try again." - -#: src/domain/loaders/load-domain-connections-by-user-id.js:384 -msgid "Unable to query domain(s). Please try again." -msgstr "Unable to query domain(s). Please try again." - -#: src/user/mutations/refresh-tokens.js:49 -#: src/user/mutations/refresh-tokens.js:63 -#: src/user/mutations/refresh-tokens.js:78 -#: src/user/mutations/refresh-tokens.js:93 -#: src/user/mutations/refresh-tokens.js:105 -#: src/user/mutations/refresh-tokens.js:142 -#: src/user/mutations/refresh-tokens.js:151 -msgid "Unable to refresh tokens, please sign in." -msgstr "Unable to refresh tokens, please sign in." - -#: src/affiliation/mutations/remove-user-from-org.js:122 -msgid "Unable to remove a user that already does not belong to this organization." -msgstr "Unable to remove a user that already does not belong to this organization." - -#: src/domain/mutations/remove-domain.js:79 -msgid "Unable to remove domain from unknown organization." -msgstr "Unable to remove domain from unknown organization." - -#: src/domain/mutations/remove-domain.js:125 -#: src/domain/mutations/remove-domain.js:139 -#: src/domain/mutations/remove-domain.js:178 -#: src/domain/mutations/remove-domain.js:197 -#: src/domain/mutations/remove-domain.js:332 -#: src/domain/mutations/remove-domain.js:359 -#: src/domain/mutations/remove-domain.js:382 -#: src/domain/mutations/remove-domain.js:393 -msgid "Unable to remove domain. Please try again." -msgstr "Unable to remove domain. Please try again." - -#: src/organization/mutations/remove-organization.js:112 -#: src/organization/mutations/remove-organization.js:124 -#: src/organization/mutations/remove-organization.js:156 -#: src/organization/mutations/remove-organization.js:173 -#: src/organization/mutations/remove-organization.js:205 -#: src/organization/mutations/remove-organization.js:217 -#: src/organization/mutations/remove-organization.js:256 -#: src/organization/mutations/remove-organization.js:378 -#: src/organization/mutations/remove-organization.js:411 -#: src/organization/mutations/remove-organization.js:449 -#: src/organization/mutations/remove-organization.js:460 -msgid "Unable to remove organization. Please try again." -msgstr "Unable to remove organization. Please try again." - -#: src/user/mutations/remove-phone-number.js:63 -#: src/user/mutations/remove-phone-number.js:74 -msgid "Unable to remove phone number. Please try again." -msgstr "Unable to remove phone number. Please try again." - -#: src/domain/mutations/remove-domain.js:63 -msgid "Unable to remove unknown domain." -msgstr "Unable to remove unknown domain." - -#: src/organization/mutations/remove-organization.js:56 -msgid "Unable to remove unknown organization." -msgstr "Unable to remove unknown organization." - -#: src/affiliation/mutations/remove-user-from-org.js:89 -msgid "Unable to remove unknown user from organization." -msgstr "Unable to remove unknown user from organization." - -#: src/affiliation/mutations/remove-user-from-org.js:75 -msgid "Unable to remove user from organization." -msgstr "Unable to remove user from organization." - -#: src/affiliation/mutations/remove-user-from-org.js:109 -#: src/affiliation/mutations/remove-user-from-org.js:136 -#: src/affiliation/mutations/remove-user-from-org.js:179 -#: src/affiliation/mutations/remove-user-from-org.js:190 -msgid "Unable to remove user from this organization. Please try again." -msgstr "Unable to remove user from this organization. Please try again." - -#: src/affiliation/mutations/remove-user-from-org.js:61 -msgid "Unable to remove user from unknown organization." -msgstr "Unable to remove user from unknown organization." - -#: src/domain/mutations/request-scan.js:57 -msgid "Unable to request a one time scan on an unknown domain." -msgstr "Unable to request a one time scan on an unknown domain." - -#: src/user/mutations/reset-password.js:95 -msgid "Unable to reset password. Please request a new email." -msgstr "Unable to reset password. Please request a new email." - -#: src/user/mutations/reset-password.js:82 -#: src/user/mutations/reset-password.js:152 -#: src/user/mutations/reset-password.js:161 -msgid "Unable to reset password. Please try again." -msgstr "Unable to reset password. Please try again." - -#: src/domain/objects/domain.js:160 -#: src/domain/objects/domain.js:203 -msgid "Unable to retrieve DMARC report information for: {domain}" -msgstr "Unable to retrieve DMARC report information for: {domain}" - -#: src/dmarc-summaries/loaders/load-start-date-from-period.js:33 -#: src/dmarc-summaries/loaders/load-start-date-from-period.js:48 -msgid "Unable to select DMARC report(s) for this period and year." -msgstr "Unable to select DMARC report(s) for this period and year." - -#: src/notify/notify-send-authenticate-email.js:21 -msgid "Unable to send authentication email. Please try again." -msgstr "Unable to send authentication email. Please try again." - -#: src/notify/notify-send-authenticate-text-msg.js:35 -msgid "Unable to send authentication text message. Please try again." -msgstr "Unable to send authentication text message. Please try again." - -#: src/notify/notify-send-org-invite-create-account.js:29 -#: src/notify/notify-send-org-invite-email.js:25 -msgid "Unable to send org invite email. Please try again." -msgstr "Unable to send org invite email. Please try again." - -#: src/notify/notify-send-password-reset-email.js:27 -msgid "Unable to send password reset email. Please try again." -msgstr "Unable to send password reset email. Please try again." - -#: src/notify/notify-send-tfa-text-msg.js:27 -msgid "Unable to send two factor authentication message. Please try again." -msgstr "Unable to send two factor authentication message. Please try again." - -#: src/notify/notify-send-verification-email.js:29 -msgid "Unable to send verification email. Please try again." -msgstr "Unable to send verification email. Please try again." - -#: src/user/mutations/set-phone-number.js:108 -#: src/user/mutations/set-phone-number.js:117 -msgid "Unable to set phone number, please try again." -msgstr "Unable to set phone number, please try again." - -#: src/user/mutations/sign-in.js:112 -#: src/user/mutations/sign-in.js:149 -#: src/user/mutations/sign-in.js:158 -#: src/user/mutations/sign-in.js:204 -#: src/user/mutations/sign-in.js:213 -#: src/user/mutations/sign-in.js:276 -#: src/user/mutations/sign-in.js:285 -msgid "Unable to sign in, please try again." -msgstr "Unable to sign in, please try again." - -#: src/user/mutations/sign-up.js:204 -#: src/user/mutations/sign-up.js:218 -msgid "Unable to sign up, please contact org admin for a new invite." -msgstr "Unable to sign up, please contact org admin for a new invite." - -#: src/user/mutations/sign-up.js:172 -#: src/user/mutations/sign-up.js:182 -#: src/user/mutations/sign-up.js:240 -#: src/user/mutations/sign-up.js:250 -msgid "Unable to sign up. Please try again." -msgstr "Unable to sign up. Please try again." - -#: src/affiliation/mutations/transfer-org-ownership.js:131 -#: src/affiliation/mutations/transfer-org-ownership.js:178 -#: src/affiliation/mutations/transfer-org-ownership.js:202 -#: src/affiliation/mutations/transfer-org-ownership.js:214 -msgid "Unable to transfer organization ownership. Please try again." -msgstr "Unable to transfer organization ownership. Please try again." - -#: src/affiliation/mutations/transfer-org-ownership.js:78 -msgid "Unable to transfer ownership of a verified organization." -msgstr "Unable to transfer ownership of a verified organization." - -#: src/affiliation/mutations/transfer-org-ownership.js:112 -msgid "Unable to transfer ownership of an org to an undefined user." -msgstr "Unable to transfer ownership of an org to an undefined user." - -#: src/affiliation/mutations/transfer-org-ownership.js:65 -msgid "Unable to transfer ownership of undefined organization." -msgstr "Unable to transfer ownership of undefined organization." - -#: src/affiliation/mutations/transfer-org-ownership.js:144 -msgid "Unable to transfer ownership to a user outside the org. Please invite the user and try again." -msgstr "Unable to transfer ownership to a user outside the org. Please invite the user and try again." - -#: src/user/mutations/verify-phone-number.js:92 -#: src/user/mutations/verify-phone-number.js:103 -msgid "Unable to two factor authenticate. Please try again." -msgstr "Unable to two factor authenticate. Please try again." - -#: src/domain/mutations/update-domain.js:93 -msgid "Unable to update domain in an unknown org." -msgstr "Unable to update domain in an unknown org." - -#: src/domain/mutations/update-domain.js:141 -msgid "Unable to update domain that does not belong to the given organization." -msgstr "Unable to update domain that does not belong to the given organization." - -#: src/domain/mutations/update-domain.js:130 -#: src/domain/mutations/update-domain.js:177 -#: src/domain/mutations/update-domain.js:187 -msgid "Unable to update domain. Please try again." -msgstr "Unable to update domain. Please try again." - -#: src/organization/mutations/update-organization.js:176 -#: src/organization/mutations/update-organization.js:208 -#: src/organization/mutations/update-organization.js:220 -#: src/organization/mutations/update-organization.js:275 -#: src/organization/mutations/update-organization.js:286 -msgid "Unable to update organization. Please try again." -msgstr "Unable to update organization. Please try again." - -#: src/user/mutations/update-user-password.js:62 -msgid "Unable to update password, current password does not match. Please try again." -msgstr "Unable to update password, current password does not match. Please try again." - -#: src/user/mutations/update-user-password.js:76 -msgid "Unable to update password, new passwords do not match. Please try again." -msgstr "Unable to update password, new passwords do not match. Please try again." - -#: src/user/mutations/update-user-password.js:90 -msgid "Unable to update password, passwords do not match requirements. Please try again." -msgstr "Unable to update password, passwords do not match requirements. Please try again." - -#: src/user/mutations/update-user-password.js:119 -#: src/user/mutations/update-user-password.js:128 -msgid "Unable to update password. Please try again." -msgstr "Unable to update password. Please try again." - -#: src/user/mutations/update-user-profile.js:134 -#: src/user/mutations/update-user-profile.js:143 -msgid "Unable to update profile. Please try again." -msgstr "Unable to update profile. Please try again." - -#: src/affiliation/mutations/update-user-role.js:97 -msgid "Unable to update role: organization unknown." -msgstr "Unable to update role: organization unknown." - -#: src/affiliation/mutations/update-user-role.js:143 -msgid "Unable to update role: user does not belong to organization." -msgstr "Unable to update role: user does not belong to organization." - -#: src/affiliation/mutations/update-user-role.js:83 -msgid "Unable to update role: user unknown." -msgstr "Unable to update role: user unknown." - -#: src/domain/mutations/update-domain.js:79 -msgid "Unable to update unknown domain." -msgstr "Unable to update unknown domain." - -#: src/organization/mutations/update-organization.js:141 -msgid "Unable to update unknown organization." -msgstr "Unable to update unknown organization." - -#: src/affiliation/mutations/update-user-role.js:131 -#: src/affiliation/mutations/update-user-role.js:156 -#: src/affiliation/mutations/update-user-role.js:251 -#: src/affiliation/mutations/update-user-role.js:262 -msgid "Unable to update user's role. Please try again." -msgstr "Unable to update user's role. Please try again." - -#: src/affiliation/mutations/update-user-role.js:69 -msgid "Unable to update your own role." -msgstr "Unable to update your own role." - -#: src/user/mutations/verify-account.js:52 -#: src/user/mutations/verify-account.js:70 -msgid "Unable to verify account. Please request a new email." -msgstr "Unable to verify account. Please request a new email." - -#: src/user/mutations/verify-account.js:99 -#: src/user/mutations/verify-account.js:108 -msgid "Unable to verify account. Please try again." -msgstr "Unable to verify account. Please try again." - -#: src/user/queries/is-user-super-admin.js:23 -msgid "Unable to verify if user is a super admin, please try again." -msgstr "Unable to verify if user is a super admin, please try again." - -#: src/user/queries/is-user-admin.js:58 -msgid "Unable to verify if user is an admin, please try again." -msgstr "Unable to verify if user is an admin, please try again." - -#: src/organization/mutations/verify-organization.js:115 -#: src/organization/mutations/verify-organization.js:137 -#: src/organization/mutations/verify-organization.js:148 -msgid "Unable to verify organization. Please try again." -msgstr "Unable to verify organization. Please try again." - -#: src/organization/mutations/verify-organization.js:54 -msgid "Unable to verify unknown organization." -msgstr "Unable to verify unknown organization." - -#: src/user/queries/find-user-by-username.js:41 -msgid "User could not be queried." -msgstr "User could not be queried." - -#: src/affiliation/mutations/update-user-role.js:272 -msgid "User role was updated successfully." -msgstr "User role was updated successfully." - -#: src/user/mutations/update-user-profile.js:75 -msgid "Username not available, please try another." -msgstr "Username not available, please try another." - -#: src/auth/verified-required.js:15 -msgid "Verification error. Please verify your account via email to access content." -msgstr "Verification error. Please verify your account via email to access content." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:71 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:161 -msgid "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:89 -msgid "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:114 -msgid "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:138 -msgid "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:184 -msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:138 -#: src/domain/loaders/load-domain-connections-by-user-id.js:147 -msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `Domain` connection." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:85 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:89 -msgid "You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:147 -msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:178 -#: src/organization/loaders/load-organization-connections-by-user-id.js:177 -msgid "You must provide a `first` or `last` value to properly paginate the `Organization` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `Organization` connection." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:133 -msgid "You must provide a `first` or `last` value to properly paginate the `SPF` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `SPF` connection." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:171 -msgid "You must provide a `first` or `last` value to properly paginate the `SSL` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `SSL` connection." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:117 -#: src/verified-domains/loaders/load-verified-domain-connections.js:117 -msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:167 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:165 -msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." -msgstr "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:26 -msgid "You must provide a `period` value to access the `DmarcSummaries` connection." -msgstr "You must provide a `period` value to access the `DmarcSummaries` connection." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:38 -msgid "You must provide a `year` value to access the `DmarcSummaries` connection." -msgstr "You must provide a `year` value to access the `DmarcSummaries` connection." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:118 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:231 -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:185 -#: src/domain/loaders/load-domain-connections-by-user-id.js:194 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:170 -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:136 -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:186 -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:180 -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:136 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:225 -#: src/organization/loaders/load-organization-connections-by-user-id.js:224 -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:164 -#: src/verified-domains/loaders/load-verified-domain-connections.js:164 -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:214 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:212 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:194 -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:218 -msgid "`{argSet}` must be of type `number` not `{typeSet}`." -msgstr "`{argSet}` must be of type `number` not `{typeSet}`." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:92 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:182 -msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." -msgstr "`{argSet}` on the `Affiliation` connection cannot be less than zero." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:110 -msgid "`{argSet}` on the `DKIMResults` connection cannot be less than zero." -msgstr "`{argSet}` on the `DKIMResults` connection cannot be less than zero." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:135 -msgid "`{argSet}` on the `DKIM` connection cannot be less than zero." -msgstr "`{argSet}` on the `DKIM` connection cannot be less than zero." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:160 -msgid "`{argSet}` on the `DMARC` connection cannot be less than zero." -msgstr "`{argSet}` on the `DMARC` connection cannot be less than zero." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." -msgstr "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." -msgstr "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:205 -msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." -msgstr "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:159 -#: src/domain/loaders/load-domain-connections-by-user-id.js:168 -msgid "`{argSet}` on the `Domain` connection cannot be less than zero." -msgstr "`{argSet}` on the `Domain` connection cannot be less than zero." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `FullPassTable` connection cannot be less than zero." -msgstr "`{argSet}` on the `FullPassTable` connection cannot be less than zero." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:106 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:110 -msgid "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." -msgstr "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:168 -msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." -msgstr "`{argSet}` on the `HTTPS` connection cannot be less than zero." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:199 -#: src/organization/loaders/load-organization-connections-by-user-id.js:198 -msgid "`{argSet}` on the `Organization` connection cannot be less than zero." -msgstr "`{argSet}` on the `Organization` connection cannot be less than zero." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:154 -msgid "`{argSet}` on the `SPF` connection cannot be less than zero." -msgstr "`{argSet}` on the `SPF` connection cannot be less than zero." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:192 -msgid "`{argSet}` on the `SSL` connection cannot be less than zero." -msgstr "`{argSet}` on the `SSL` connection cannot be less than zero." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." -msgstr "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:138 -#: src/verified-domains/loaders/load-verified-domain-connections.js:138 -msgid "`{argSet}` on the `VerifiedDomain` connection cannot be less than zero." -msgstr "`{argSet}` on the `VerifiedDomain` connection cannot be less than zero." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:188 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:186 -msgid "`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero." -msgstr "`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero." diff --git a/api-js/src/locale/fr/messages.js b/api-js/src/locale/fr/messages.js deleted file mode 100644 index 5032c1f6e6..0000000000 --- a/api-js/src/locale/fr/messages.js +++ /dev/null @@ -1,677 +0,0 @@ -/*eslint-disable*/ module.exports = { - messages: { - 'Authentication error. Please sign in.': - "Erreur d'authentification. Veuillez vous connecter.", - 'Cannot query affiliations on organization without admin permission or higher.': - "Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus.", - 'Email already in use.': 'Courriel déjà utilisé.', - 'If an account with this username is found, a password reset link will be found in your inbox.': - "Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception.", - 'If an account with this username is found, an email verification link will be found in your inbox.': - "Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.", - 'Incorrect TFA code. Please sign in again.': - 'Code TFA incorrect. Veuillez vous reconnecter.', - 'Incorrect token value. Please request a new email.': - 'La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.', - 'Incorrect username or password. Please try again.': - "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.", - 'Invalid token, please sign in.': - 'Jeton invalide, veuillez vous connecter.', - 'New passwords do not match.': - 'Les nouveaux mots de passe ne correspondent pas.', - 'No organization with the provided slug could be found.': - "Aucune organisation avec le slug fourni n'a pu être trouvée.", - 'No verified domain with the provided domain could be found.': - "Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.", - 'No verified organization with the provided slug could be found.': - "Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.", - 'Organization has already been verified.': - "L'organisation a déjà été vérifiée.", - 'Organization name already in use, please choose another and try again.': - "Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.", - 'Organization name already in use. Please try again with a different name.': - "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.", - 'Ownership check error. Unable to request domain information.': - 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', - 'Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `DKIM` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `DMARC` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `Domain` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `Organization` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `SPF` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `SSL` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté.", - 'Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.': - "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté.", - 'Password does not meet requirements.': - 'Le mot de passe ne répond pas aux exigences.', - 'Password was successfully reset.': - 'Le mot de passe a été réinitialisé avec succès.', - 'Password was successfully updated.': - 'Le mot de passe a été mis à jour avec succès.', - 'Passwords do not match.': 'Les mots de passe ne correspondent pas.', - 'Permission Denied: Could not retrieve specified organization.': - "Permission refusée : Impossible de récupérer l'organisation spécifiée.", - 'Permission Denied: Please contact org owner to transfer ownership.': - "Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété.", - 'Permission Denied: Please contact organization admin for help with removing domain.': - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.", - 'Permission Denied: Please contact organization admin for help with removing organization.': - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.", - 'Permission Denied: Please contact organization admin for help with removing users.': - "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - 'Permission Denied: Please contact organization admin for help with updating organization.': - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - 'Permission Denied: Please contact organization admin for help with updating user roles.': - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - 'Permission Denied: Please contact organization admin for help with user invitations.': - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", - 'Permission Denied: Please contact organization admin for help with user role changes.': - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.", - 'Permission Denied: Please contact organization user for help with creating domain.': - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.", - 'Permission Denied: Please contact organization user for help with retrieving this domain.': - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine.", - 'Permission Denied: Please contact organization user for help with scanning this domain.': - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.", - 'Permission Denied: Please contact organization user for help with updating this domain.': - "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", - 'Permission Denied: Please contact super admin for help with removing domain.': - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", - 'Permission Denied: Please contact super admin for help with removing organization.': - "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.", - 'Permission Denied: Please contact super admin for help with verifying this organization.': - "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.", - 'Permission check error. Unable to request domain information.': - 'Erreur de vérification des permissions. Impossible de demander des informations sur le domaine.', - 'Permission error, not an admin for this user.': - "Erreur de permission, pas d'administrateur pour cet utilisateur.", - "Permission error: Unable to close other user's account.": - "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.", - 'Phone number has been successfully removed.': - 'Le numéro de téléphone a été supprimé avec succès.', - 'Phone number has been successfully set, you will receive a verification text message shortly.': - 'Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification.', - 'Profile successfully updated.': 'Le profil a été mis à jour avec succès.', - 'Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `Affiliation` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `DkimFailureTable` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `DkimFailureTable` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `DmarcSummaries` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `Domain` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `FullPassTable` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `GuidanceTag` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `Organization` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `SpfFailureTable` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `VerifiedDomain` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.': - [ - "La demande d'enregistrements `", - ['amount'], - '` sur la connexion `VerifiedOrganization` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'La demande de ', - ['amount'], - ' enregistrements sur la connexion `DKIMResults` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'La demande de ', - ['amount'], - ' enregistrements sur la connexion `DKIM` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'La demande de ', - ['amount'], - ' enregistrements sur la connexion `DMARC` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'La demande de ', - ['amount'], - ' enregistrements sur la connexion `HTTPS` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'La demande de ', - ['amount'], - ' enregistrements sur la connexion `SPF` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.': - [ - 'La demande de ', - ['amount'], - ' enregistrements sur la connexion `SSL` dépasse la limite `', - ['argSet'], - '` de 100 enregistrements.', - ], - 'Successfully closed account.': 'Le compte a été fermé avec succès.', - 'Successfully dispatched one time scan.': - 'Un seul balayage a été effectué avec succès.', - 'Successfully email verified account, and set TFA send method to email.': - "Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.", - 'Successfully invited user to organization, and sent notification email.': - "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.", - 'Successfully left organization: {0}': [ - "L'organisation a été quittée avec succès: ", - ['0'], - ], - 'Successfully removed domain: {0} from {1}.': [ - 'A réussi à supprimer le domaine : ', - ['0'], - ' de ', - ['1'], - '.', - ], - 'Successfully removed organization: {0}.': [ - "A réussi à supprimer l'organisation : ", - ['0'], - '.', - ], - 'Successfully removed user from organization.': - "L'utilisateur a été retiré de l'organisation avec succès.", - 'Successfully sent invitation to service, and organization email.': - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - 'Successfully signed out.': "J'ai réussi à me déconnecter.", - 'Successfully transferred org: {0} ownership to user: {1}': [ - 'A réussi à transférer la propriété de org: ', - ['0'], - " à l'utilisateur: ", - ['1'], - ], - 'Successfully verified organization: {0}.': - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - 'Successfully verified phone number, and set TFA send method to text.': - "Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte.", - 'Token value incorrect, please sign in again.': - 'La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.', - 'Too many failed login attempts, please reset your password, and try again.': - 'Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer.', - 'Two factor code is incorrect. Please try again.': - 'Le code à deux facteurs est incorrect. Veuillez réessayer.', - 'Two factor code length is incorrect. Please try again.': - 'La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.', - 'Unable leave organization. Please try again.': - "Impossible de quitter l'organisation. Veuillez réessayer.", - 'Unable to authenticate. Please try again.': - "Impossible de s'authentifier. Veuillez réessayer.", - 'Unable to check permission. Please try again.': - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - 'Unable to close account of an undefined user.': - "Impossible de fermer le compte d'un utilisateur non défini.", - 'Unable to close account. Please try again.': - 'Impossible de fermer le compte. Veuillez réessayer.', - 'Unable to create domain in unknown organization.': - 'Impossible de créer un domaine dans une organisation inconnue.', - 'Unable to create domain, organization has already claimed it.': - "Impossible de créer le domaine, l'organisation l'a déjà réclamé.", - 'Unable to create domain. Please try again.': - 'Impossible de créer un domaine. Veuillez réessayer.', - 'Unable to create organization. Please try again.': - 'Impossible de créer une organisation. Veuillez réessayer.', - 'Unable to dispatch one time scan. Please try again.': - "Impossible d'envoyer un scan unique. Veuillez réessayer.", - 'Unable to find Aggregate guidance tag(s). Please try again.': - "Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.", - 'Unable to find DKIM guidance tag(s). Please try again.': - "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.", - 'Unable to find DKIM result(s). Please try again.': - 'Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.', - 'Unable to find DKIM scan(s). Please try again.': - 'Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.', - 'Unable to find DMARC guidance tag(s). Please try again.': - "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.", - 'Unable to find DMARC scan(s). Please try again.': - 'Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.', - 'Unable to find DMARC summary data. Please try again.': - 'Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.', - 'Unable to find HTTPS guidance tag(s). Please try again.': - "Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.", - 'Unable to find HTTPS scan(s). Please try again.': - 'Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.', - 'Unable to find SPF guidance tag(s). Please try again.': - "Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.", - 'Unable to find SPF scan(s). Please try again.': - 'Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.', - 'Unable to find SSL guidance tag(s). Please try again.': - "Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.", - 'Unable to find SSL scan(s). Please try again.': - 'Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.', - 'Unable to find the requested domain.': - 'Impossible de trouver le domaine demandé.', - 'Unable to find user affiliation(s). Please try again.': - "Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.", - 'Unable to find verified organization(s). Please try again.': - 'Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.', - 'Unable to invite user to unknown organization.': - "Impossible d'inviter un utilisateur à une organisation inconnue.", - 'Unable to invite user. Please try again.': - "Impossible d'inviter un utilisateur. Veuillez réessayer.", - 'Unable to invite yourself to an org.': "Impossible de s'inviter à un org.", - 'Unable to leave undefined organization.': - 'Impossible de quitter une organisation non définie.', - 'Unable to load Aggregate guidance tag(s). Please try again.': - "Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.", - 'Unable to load DKIM failure data. Please try again.': - "Impossible de charger les données d'échec DKIM. Veuillez réessayer.", - 'Unable to load DKIM guidance tag(s). Please try again.': - "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.", - 'Unable to load DKIM result(s). Please try again.': - 'Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.', - 'Unable to load DKIM scan(s). Please try again.': - 'Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.', - 'Unable to load DMARC failure data. Please try again.': - "Impossible de charger les données d'échec DMARC. Veuillez réessayer.", - 'Unable to load DMARC guidance tag(s). Please try again.': - "Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.", - 'Unable to load DMARC scan(s). Please try again.': - 'Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.', - 'Unable to load DMARC summary data. Please try again.': - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - 'Unable to load HTTPS guidance tag(s). Please try again.': - "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.", - 'Unable to load HTTPS scan(s). Please try again.': - 'Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.', - 'Unable to load SPF failure data. Please try again.': - "Impossible de charger les données d'échec SPF. Veuillez réessayer.", - 'Unable to load SPF guidance tag(s). Please try again.': - "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.", - 'Unable to load SPF scan(s). Please try again.': - 'Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.', - 'Unable to load SSL guidance tag(s). Please try again.': - "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.", - 'Unable to load SSL scan(s). Please try again.': - 'Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.', - 'Unable to load affiliation information. Please try again.': - "Impossible de charger les informations d'affiliation. Veuillez réessayer.", - 'Unable to load affiliation(s). Please try again.': - "Impossible de charger l'affiliation (s). Veuillez réessayer.", - 'Unable to load domain(s). Please try again.': - 'Impossible de charger le(s) domaine(s). Veuillez réessayer.', - 'Unable to load domain. Please try again.': - 'Impossible de charger le domaine. Veuillez réessayer.', - 'Unable to load full pass data. Please try again.': - 'Impossible de charger les données complètes de la passe. Veuillez réessayer.', - 'Unable to load mail summary. Please try again.': - 'Impossible de charger le résumé du courrier. Veuillez réessayer.', - 'Unable to load organization(s). Please try again.': - "Impossible de charger l'organisation (s). Veuillez réessayer.", - 'Unable to load owner information. Please try again.': - 'Impossible de charger les informations sur le propriétaire. Veuillez réessayer.', - 'Unable to load summary. Please try again.': - 'Impossible de charger le résumé. Veuillez réessayer.', - 'Unable to load user(s). Please try again.': - 'Impossible de charger le(s) utilisateur(s). Veuillez réessayer.', - 'Unable to load verified domain(s). Please try again.': - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - 'Unable to load verified organization(s). Please try again.': - 'Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.', - 'Unable to load web summary. Please try again.': - 'Impossible de charger le résumé web. Veuillez réessayer.', - 'Unable to query affiliation(s). Please try again.': - "Impossible de demander l'affiliation (s). Veuillez réessayer.", - 'Unable to query domain(s). Please try again.': - "Impossible d'interroger le(s) domaine(s). Veuillez réessayer.", - 'Unable to refresh tokens, please sign in.': - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', - 'Unable to remove a user that already does not belong to this organization.': - "Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation.", - 'Unable to remove domain from unknown organization.': - "Impossible de supprimer le domaine d'une organisation inconnue.", - 'Unable to remove domain. Please try again.': - 'Impossible de supprimer le domaine. Veuillez réessayer.', - 'Unable to remove organization. Please try again.': - "Impossible de supprimer l'organisation. Veuillez réessayer.", - 'Unable to remove phone number. Please try again.': - 'Impossible de supprimer le numéro de téléphone. Veuillez réessayer.', - 'Unable to remove unknown domain.': - 'Impossible de supprimer un domaine inconnu.', - 'Unable to remove unknown organization.': - 'Impossible de supprimer une organisation inconnue.', - 'Unable to remove unknown user from organization.': - "Impossible de supprimer un utilisateur inconnu de l'organisation.", - 'Unable to remove user from organization.': - "Impossible de supprimer un utilisateur de l'organisation.", - 'Unable to remove user from this organization. Please try again.': - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - 'Unable to remove user from unknown organization.': - "Impossible de supprimer un utilisateur d'une organisation inconnue.", - 'Unable to request a one time scan on an unknown domain.': - 'Impossible de demander un scan unique sur un domaine inconnu.', - 'Unable to reset password. Please request a new email.': - 'Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.', - 'Unable to reset password. Please try again.': - 'Impossible de réinitialiser le mot de passe. Veuillez réessayer.', - 'Unable to retrieve DMARC report information for: {domain}': [ - 'Impossible de récupérer les informations du rapport DMARC pour : ', - ['domain'], - ], - 'Unable to select DMARC report(s) for this period and year.': - 'Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année.', - 'Unable to send authentication email. Please try again.': - "Impossible d'envoyer l'email d'authentification. Veuillez réessayer.", - 'Unable to send authentication text message. Please try again.': - "Impossible d'envoyer un message texte d'authentification. Veuillez réessayer.", - 'Unable to send org invite email. Please try again.': - "Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.", - 'Unable to send password reset email. Please try again.': - "Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.", - 'Unable to send two factor authentication message. Please try again.': - "Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.", - 'Unable to send verification email. Please try again.': - "Impossible d'envoyer l'email de vérification. Veuillez réessayer.", - 'Unable to set phone number, please try again.': - 'Impossible de définir le numéro de téléphone, veuillez réessayer.', - 'Unable to sign in, please try again.': - 'Impossible de se connecter, veuillez réessayer.', - 'Unable to sign up, please contact org admin for a new invite.': - "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.", - 'Unable to sign up. Please try again.': - "Impossible de s'inscrire. Veuillez réessayer.", - 'Unable to transfer organization ownership. Please try again.': - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - 'Unable to transfer ownership of a verified organization.': - "Impossible de transférer la propriété d'une organisation vérifiée.", - 'Unable to transfer ownership of an org to an undefined user.': - "Impossible de transférer la propriété d'un org à un utilisateur non défini.", - 'Unable to transfer ownership of undefined organization.': - "Impossible de transférer la propriété d'une organisation non définie.", - 'Unable to transfer ownership to a user outside the org. Please invite the user and try again.': - "Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer.", - 'Unable to two factor authenticate. Please try again.': - "Impossible de s'authentifier par deux facteurs. Veuillez réessayer.", - 'Unable to update domain in an unknown org.': - 'Impossible de mettre à jour le domaine dans un org inconnu.', - 'Unable to update domain that does not belong to the given organization.': - "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.", - 'Unable to update domain. Please try again.': - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - 'Unable to update organization. Please try again.': - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - 'Unable to update password, current password does not match. Please try again.': - 'Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer.', - 'Unable to update password, new passwords do not match. Please try again.': - 'Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer.', - 'Unable to update password, passwords do not match requirements. Please try again.': - 'Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer.', - 'Unable to update password. Please try again.': - 'Impossible de mettre à jour le mot de passe. Veuillez réessayer.', - 'Unable to update profile. Please try again.': - 'Impossible de mettre à jour le profil. Veuillez réessayer.', - 'Unable to update role: organization unknown.': - 'Impossible de mettre à jour le rôle : organisation inconnue.', - 'Unable to update role: user does not belong to organization.': - "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.", - 'Unable to update role: user unknown.': - 'Impossible de mettre à jour le rôle : utilisateur inconnu.', - 'Unable to update unknown domain.': - 'Impossible de mettre à jour un domaine inconnu.', - 'Unable to update unknown organization.': - 'Impossible de mettre à jour une organisation inconnue.', - "Unable to update user's role. Please try again.": - "Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.", - 'Unable to update your own role.': - 'Impossible de mettre à jour votre propre rôle.', - 'Unable to verify account. Please request a new email.': - 'Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.', - 'Unable to verify account. Please try again.': - 'Impossible de vérifier le compte. Veuillez réessayer.', - 'Unable to verify if user is a super admin, please try again.': - "Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer.", - 'Unable to verify if user is an admin, please try again.': - "Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.", - 'Unable to verify organization. Please try again.': - "Impossible de vérifier l'organisation. Veuillez réessayer.", - 'Unable to verify unknown organization.': - 'Impossible de vérifier une organisation inconnue.', - 'User could not be queried.': "L'utilisateur n'a pas pu être interrogé.", - 'User role was updated successfully.': - "Le rôle de l'utilisateur a été mis à jour avec succès.", - 'Username not available, please try another.': - "Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.", - 'Verification error. Please verify your account via email to access content.': - 'Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.', - 'You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`.', - 'You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.', - 'You must provide a `first` or `last` value to properly paginate the `DKIM` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.', - 'You must provide a `first` or `last` value to properly paginate the `DMARC` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.', - 'You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`.', - 'You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`.', - 'You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`.', - 'You must provide a `first` or `last` value to properly paginate the `Domain` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`.', - 'You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`.', - 'You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.', - 'You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.', - 'You must provide a `first` or `last` value to properly paginate the `Organization` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`.', - 'You must provide a `first` or `last` value to properly paginate the `SPF` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.', - 'You must provide a `first` or `last` value to properly paginate the `SSL` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.', - 'You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`.', - 'You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`.', - 'You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.': - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`.', - 'You must provide a `period` value to access the `DmarcSummaries` connection.': - 'Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.', - 'You must provide a `year` value to access the `DmarcSummaries` connection.': - 'Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.', - '`{argSet}` must be of type `number` not `{typeSet}`.': [ - '`', - ['argSet'], - '` doit être de type `number` et non `', - ['typeSet'], - '`.', - ], - '`{argSet}` on the `Affiliation` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `Affiliation` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `DKIMResults` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `DKIMResults` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `DKIM` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `DKIM` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `DMARC` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `DMARC` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `Domain` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `Domain` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `FullPassTable` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `FullPassTable` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `GuidanceTag` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.', - ], - '`{argSet}` on the `HTTPS` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `HTTPS` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `Organization` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `Organization` ne peut être inférieure à zéro.', - ], - '`{argSet}` on the `SPF` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `SPF` ne peut être inférieure à zéro.', - ], - '`{argSet}` on the `SSL` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `SSL` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.': [ - '`', - ['argSet'], - '` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.', - ], - '`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.': - [ - '`', - ['argSet'], - '` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.', - ], - }, -} diff --git a/api-js/src/locale/fr/messages.po b/api-js/src/locale/fr/messages.po deleted file mode 100644 index 2e18a99bbf..0000000000 --- a/api-js/src/locale/fr/messages.po +++ /dev/null @@ -1,1275 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language: \n" -"Language-Team: \n" -"Content-Type: \n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#: src/auth/check-permission.js:20 -#: src/auth/check-permission.js:48 -#: src/auth/user-required.js:10 -#: src/auth/user-required.js:21 -#: src/auth/user-required.js:28 -msgid "Authentication error. Please sign in." -msgstr "Erreur d'authentification. Veuillez vous connecter." - -#: src/organization/objects/organization.js:142 -msgid "Cannot query affiliations on organization without admin permission or higher." -msgstr "Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus." - -#: src/user/mutations/sign-up.js:116 -msgid "Email already in use." -msgstr "Courriel déjà utilisé." - -#: src/user/mutations/send-password-reset.js:62 -msgid "If an account with this username is found, a password reset link will be found in your inbox." -msgstr "Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception." - -#: src/user/mutations/send-email-verification.js:61 -msgid "If an account with this username is found, an email verification link will be found in your inbox." -msgstr "Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception." - -#: src/user/mutations/authenticate.js:177 -msgid "Incorrect TFA code. Please sign in again." -msgstr "Code TFA incorrect. Veuillez vous reconnecter." - -#: src/user/mutations/reset-password.js:66 -msgid "Incorrect token value. Please request a new email." -msgstr "La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail." - -#: src/user/mutations/sign-in.js:70 -#: src/user/mutations/sign-in.js:295 -msgid "Incorrect username or password. Please try again." -msgstr "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer." - -#: src/auth/verify-jwt.js:14 -msgid "Invalid token, please sign in." -msgstr "Jeton invalide, veuillez vous connecter." - -#: src/user/mutations/reset-password.js:108 -msgid "New passwords do not match." -msgstr "Les nouveaux mots de passe ne correspondent pas." - -#: src/organization/queries/find-organization-by-slug.js:42 -msgid "No organization with the provided slug could be found." -msgstr "Aucune organisation avec le slug fourni n'a pu être trouvée." - -#: src/verified-domains/queries/find-verified-domain-by-domain.js:33 -msgid "No verified domain with the provided domain could be found." -msgstr "Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé." - -#: src/verified-organizations/queries/find-verified-organization-by-slug.js:31 -msgid "No verified organization with the provided slug could be found." -msgstr "Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée." - -#: src/organization/mutations/verify-organization.js:82 -msgid "Organization has already been verified." -msgstr "L'organisation a déjà été vérifiée." - -#: src/organization/mutations/update-organization.js:188 -msgid "Organization name already in use, please choose another and try again." -msgstr "Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer." - -#: src/organization/mutations/create-organization.js:138 -msgid "Organization name already in use. Please try again with a different name." -msgstr "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent." - -#: src/auth/check-domain-ownership.js:30 -#: src/auth/check-domain-ownership.js:42 -#: src/auth/check-domain-ownership.js:68 -#: src/auth/check-domain-ownership.js:79 -msgid "Ownership check error. Unable to request domain information." -msgstr "Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:80 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:170 -msgid "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:98 -msgid "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:123 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:159 -msgid "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:147 -msgid "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:193 -msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:147 -#: src/domain/loaders/load-domain-connections-by-user-id.js:156 -msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:94 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:98 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:98 -msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:156 -msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:187 -#: src/organization/loaders/load-organization-connections-by-user-id.js:186 -msgid "Passing both `first` and `last` to paginate the `Organization` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:142 -msgid "Passing both `first` and `last` to paginate the `SPF` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:180 -msgid "Passing both `first` and `last` to paginate the `SSL` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:45 -msgid "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:126 -#: src/verified-domains/loaders/load-verified-domain-connections.js:126 -msgid "Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:176 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:174 -msgid "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté." - -#: src/user/mutations/reset-password.js:120 -#: src/user/mutations/sign-up.js:90 -msgid "Password does not meet requirements." -msgstr "Le mot de passe ne répond pas aux exigences." - -#: src/user/mutations/reset-password.js:168 -msgid "Password was successfully reset." -msgstr "Le mot de passe a été réinitialisé avec succès." - -#: src/user/mutations/update-user-password.js:134 -msgid "Password was successfully updated." -msgstr "Le mot de passe a été mis à jour avec succès." - -#: src/user/mutations/sign-up.js:102 -msgid "Passwords do not match." -msgstr "Les mots de passe ne correspondent pas." - -#: src/organization/queries/find-organization-by-slug.js:53 -msgid "Permission Denied: Could not retrieve specified organization." -msgstr "Permission refusée : Impossible de récupérer l'organisation spécifiée." - -#: src/affiliation/mutations/transfer-org-ownership.js:95 -msgid "Permission Denied: Please contact org owner to transfer ownership." -msgstr "Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété." - -#: src/domain/mutations/remove-domain.js:109 -msgid "Permission Denied: Please contact organization admin for help with removing domain." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine." - -#: src/organization/mutations/remove-organization.js:71 -msgid "Permission Denied: Please contact organization admin for help with removing organization." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation." - -#: src/affiliation/mutations/remove-user-from-org.js:214 -msgid "Permission Denied: Please contact organization admin for help with removing users." -msgstr "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." - -#: src/organization/mutations/update-organization.js:156 -msgid "Permission Denied: Please contact organization admin for help with updating organization." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." - -#: src/affiliation/mutations/update-user-role.js:190 -#: src/affiliation/mutations/update-user-role.js:213 -#: src/affiliation/mutations/update-user-role.js:231 -msgid "Permission Denied: Please contact organization admin for help with updating user roles." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs." - -#: src/affiliation/mutations/invite-user-to-org.js:106 -msgid "Permission Denied: Please contact organization admin for help with user invitations." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs." - -#: src/affiliation/mutations/update-user-role.js:112 -msgid "Permission Denied: Please contact organization admin for help with user role changes." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs." - -#: src/domain/mutations/create-domain.js:95 -msgid "Permission Denied: Please contact organization user for help with creating domain." -msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine." - -#: src/domain/queries/find-domain-by-domain.js:48 -msgid "Permission Denied: Please contact organization user for help with retrieving this domain." -msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine." - -#: src/domain/mutations/request-scan.js:70 -msgid "Permission Denied: Please contact organization user for help with scanning this domain." -msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine." - -#: src/domain/mutations/update-domain.js:112 -msgid "Permission Denied: Please contact organization user for help with updating this domain." -msgstr "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." - -#: src/domain/mutations/remove-domain.js:96 -msgid "Permission Denied: Please contact super admin for help with removing domain." -msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." - -#: src/organization/mutations/remove-organization.js:85 -msgid "Permission Denied: Please contact super admin for help with removing organization." -msgstr "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation." - -#: src/organization/mutations/verify-organization.js:69 -msgid "Permission Denied: Please contact super admin for help with verifying this organization." -msgstr "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation." - -#: src/auth/check-domain-permission.js:24 -#: src/auth/check-domain-permission.js:48 -#: src/auth/check-domain-permission.js:61 -msgid "Permission check error. Unable to request domain information." -msgstr "Erreur de vérification des permissions. Impossible de demander des informations sur le domaine." - -#: src/auth/check-user-is-admin-for-user.js:20 -#: src/auth/check-user-is-admin-for-user.js:30 -#: src/auth/check-user-is-admin-for-user.js:63 -#: src/auth/check-user-is-admin-for-user.js:75 -msgid "Permission error, not an admin for this user." -msgstr "Erreur de permission, pas d'administrateur pour cet utilisateur." - -#: src/user/mutations/close-account.js:54 -msgid "Permission error: Unable to close other user's account." -msgstr "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur." - -#: src/user/mutations/remove-phone-number.js:81 -msgid "Phone number has been successfully removed." -msgstr "Le numéro de téléphone a été supprimé avec succès." - -#: src/user/mutations/set-phone-number.js:131 -msgid "Phone number has been successfully set, you will receive a verification text message shortly." -msgstr "Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification." - -#: src/user/mutations/update-user-profile.js:160 -msgid "Profile successfully updated." -msgstr "Le profil a été mis à jour avec succès." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:103 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:193 -msgid "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `Affiliation` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:216 -msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `DmarcSummaries` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:170 -#: src/domain/loaders/load-domain-connections-by-user-id.js:179 -msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `Domain` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `FullPassTable` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:117 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:121 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:121 -msgid "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `GuidanceTag` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:210 -#: src/organization/loaders/load-organization-connections-by-user-id.js:209 -msgid "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `Organization` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:68 -msgid "Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `SpfFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:149 -#: src/verified-domains/loaders/load-verified-domain-connections.js:149 -msgid "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `VerifiedDomain` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:199 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:197 -msgid "Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande d'enregistrements `{amount}` sur la connexion `VerifiedOrganization` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:121 -msgid "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `DKIMResults` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:146 -msgid "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `DKIM` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:171 -msgid "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `DMARC` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:179 -msgid "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `HTTPS` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:165 -msgid "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `SPF` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:203 -msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `SSL` dépasse la limite `{argSet}` de 100 enregistrements." - -#: src/user/mutations/close-account.js:508 -msgid "Successfully closed account." -msgstr "Le compte a été fermé avec succès." - -#: src/domain/mutations/request-scan.js:131 -msgid "Successfully dispatched one time scan." -msgstr "Un seul balayage a été effectué avec succès." - -#: src/user/mutations/verify-account.js:118 -msgid "Successfully email verified account, and set TFA send method to email." -msgstr "Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email." - -#: src/affiliation/mutations/invite-user-to-org.js:195 -msgid "Successfully invited user to organization, and sent notification email." -msgstr "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé." - -#: src/affiliation/mutations/leave-organization.js:444 -msgid "Successfully left organization: {0}" -msgstr "L'organisation a été quittée avec succès: {0}" - -#: src/domain/mutations/remove-domain.js:402 -msgid "Successfully removed domain: {0} from {1}." -msgstr "A réussi à supprimer le domaine : {0} de {1}." - -#: src/organization/mutations/remove-organization.js:471 -msgid "Successfully removed organization: {0}." -msgstr "A réussi à supprimer l'organisation : {0}." - -#: src/affiliation/mutations/remove-user-from-org.js:200 -msgid "Successfully removed user from organization." -msgstr "L'utilisateur a été retiré de l'organisation avec succès." - -#: src/affiliation/mutations/invite-user-to-org.js:137 -msgid "Successfully sent invitation to service, and organization email." -msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." - -#: src/user/mutations/sign-out.js:25 -msgid "Successfully signed out." -msgstr "J'ai réussi à me déconnecter." - -#: src/affiliation/mutations/transfer-org-ownership.js:224 -msgid "Successfully transferred org: {0} ownership to user: {1}" -msgstr "A réussi à transférer la propriété de org: {0} à l'utilisateur: {1}" - -#: src/organization/mutations/verify-organization.js:157 -msgid "Successfully verified organization: {0}." -msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." - -#: src/user/mutations/verify-phone-number.js:118 -msgid "Successfully verified phone number, and set TFA send method to text." -msgstr "Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte." - -#: src/user/mutations/authenticate.js:62 -msgid "Token value incorrect, please sign in again." -msgstr "La valeur du jeton est incorrecte, veuillez vous connecter à nouveau." - -#: src/user/mutations/sign-in.js:84 -msgid "Too many failed login attempts, please reset your password, and try again." -msgstr "Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer." - -#: src/user/mutations/verify-phone-number.js:63 -msgid "Two factor code is incorrect. Please try again." -msgstr "Le code à deux facteurs est incorrect. Veuillez réessayer." - -#: src/user/mutations/verify-phone-number.js:50 -msgid "Two factor code length is incorrect. Please try again." -msgstr "La longueur du code à deux facteurs est incorrecte. Veuillez réessayer." - -#: src/affiliation/mutations/leave-organization.js:80 -#: src/affiliation/mutations/leave-organization.js:90 -#: src/affiliation/mutations/leave-organization.js:121 -#: src/affiliation/mutations/leave-organization.js:138 -#: src/affiliation/mutations/leave-organization.js:169 -#: src/affiliation/mutations/leave-organization.js:179 -#: src/affiliation/mutations/leave-organization.js:217 -#: src/affiliation/mutations/leave-organization.js:339 -#: src/affiliation/mutations/leave-organization.js:372 -#: src/affiliation/mutations/leave-organization.js:409 -#: src/affiliation/mutations/leave-organization.js:427 -#: src/affiliation/mutations/leave-organization.js:437 -msgid "Unable leave organization. Please try again." -msgstr "Impossible de quitter l'organisation. Veuillez réessayer." - -#: src/user/mutations/authenticate.js:77 -#: src/user/mutations/authenticate.js:125 -#: src/user/mutations/authenticate.js:134 -msgid "Unable to authenticate. Please try again." -msgstr "Impossible de s'authentifier. Veuillez réessayer." - -#: src/auth/check-permission.js:30 -#: src/auth/check-permission.js:58 -#: src/auth/check-super-admin.js:20 -#: src/auth/check-super-admin.js:30 -msgid "Unable to check permission. Please try again." -msgstr "Impossible de vérifier l'autorisation. Veuillez réessayer." - -#: src/user/mutations/close-account.js:67 -msgid "Unable to close account of an undefined user." -msgstr "Impossible de fermer le compte d'un utilisateur non défini." - -#: src/user/mutations/close-account.js:88 -#: src/user/mutations/close-account.js:98 -#: src/user/mutations/close-account.js:123 -#: src/user/mutations/close-account.js:133 -#: src/user/mutations/close-account.js:164 -#: src/user/mutations/close-account.js:179 -#: src/user/mutations/close-account.js:208 -#: src/user/mutations/close-account.js:218 -#: src/user/mutations/close-account.js:254 -#: src/user/mutations/close-account.js:366 -#: src/user/mutations/close-account.js:397 -#: src/user/mutations/close-account.js:422 -#: src/user/mutations/close-account.js:458 -#: src/user/mutations/close-account.js:475 -#: src/user/mutations/close-account.js:490 -#: src/user/mutations/close-account.js:499 -msgid "Unable to close account. Please try again." -msgstr "Impossible de fermer le compte. Veuillez réessayer." - -#: src/domain/mutations/create-domain.js:75 -msgid "Unable to create domain in unknown organization." -msgstr "Impossible de créer un domaine dans une organisation inconnue." - -#: src/domain/mutations/create-domain.js:151 -msgid "Unable to create domain, organization has already claimed it." -msgstr "Impossible de créer le domaine, l'organisation l'a déjà réclamé." - -#: src/domain/mutations/create-domain.js:130 -#: src/domain/mutations/create-domain.js:140 -#: src/domain/mutations/create-domain.js:189 -#: src/domain/mutations/create-domain.js:199 -#: src/domain/mutations/create-domain.js:217 -#: src/domain/mutations/create-domain.js:247 -#: src/domain/mutations/create-domain.js:265 -#: src/domain/mutations/create-domain.js:275 -msgid "Unable to create domain. Please try again." -msgstr "Impossible de créer un domaine. Veuillez réessayer." - -#: src/organization/mutations/create-organization.js:206 -#: src/organization/mutations/create-organization.js:229 -#: src/organization/mutations/create-organization.js:240 -msgid "Unable to create organization. Please try again." -msgstr "Impossible de créer une organisation. Veuillez réessayer." - -#: src/domain/mutations/request-scan.js:95 -#: src/domain/mutations/request-scan.js:109 -#: src/domain/mutations/request-scan.js:123 -msgid "Unable to dispatch one time scan. Please try again." -msgstr "Impossible d'envoyer un scan unique. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:48 -msgid "Unable to find Aggregate guidance tag(s). Please try again." -msgstr "Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:48 -msgid "Unable to find DKIM guidance tag(s). Please try again." -msgstr "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer." - -#: src/email-scan/loaders/load-dkim-result-by-key.js:20 -#: src/email-scan/loaders/load-dkim-result-by-key.js:34 -msgid "Unable to find DKIM result(s). Please try again." -msgstr "Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer." - -#: src/email-scan/loaders/load-dkim-by-key.js:19 -#: src/email-scan/loaders/load-dkim-by-key.js:31 -msgid "Unable to find DKIM scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:48 -msgid "Unable to find DMARC guidance tag(s). Please try again." -msgstr "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer." - -#: src/email-scan/loaders/load-dmarc-by-key.js:20 -#: src/email-scan/loaders/load-dmarc-by-key.js:34 -msgid "Unable to find DMARC scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer." - -#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:37 -#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:51 -msgid "Unable to find DMARC summary data. Please try again." -msgstr "Impossible de trouver les données de synthèse DMARC. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-https-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-https-guidance-tags.js:48 -msgid "Unable to find HTTPS guidance tag(s). Please try again." -msgstr "Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer." - -#: src/web-scan/loaders/load-https-by-key.js:19 -msgid "Unable to find HTTPS scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-spf-guidance-tags.js:29 -#: src/guidance-tag/loaders/load-spf-guidance-tags.js:43 -msgid "Unable to find SPF guidance tag(s). Please try again." -msgstr "Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer." - -#: src/email-scan/loaders/load-spf-by-key.js:19 -#: src/email-scan/loaders/load-spf-by-key.js:31 -msgid "Unable to find SPF scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:29 -#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:43 -msgid "Unable to find SSL guidance tag(s). Please try again." -msgstr "Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer." - -#: src/web-scan/loaders/load-ssl-by-key.js:18 -#: src/web-scan/loaders/load-ssl-by-key.js:30 -msgid "Unable to find SSL scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer." - -#: src/domain/queries/find-domain-by-domain.js:38 -msgid "Unable to find the requested domain." -msgstr "Impossible de trouver le domaine demandé." - -#: src/affiliation/loaders/load-affiliation-by-key.js:22 -#: src/affiliation/loaders/load-affiliation-by-key.js:36 -msgid "Unable to find user affiliation(s). Please try again." -msgstr "Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer." - -#: src/verified-organizations/loaders/load-verified-organization-by-key.js:32 -#: src/verified-organizations/loaders/load-verified-organization-by-key.js:44 -#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:32 -#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:44 -msgid "Unable to find verified organization(s). Please try again." -msgstr "Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer." - -#: src/affiliation/mutations/invite-user-to-org.js:87 -msgid "Unable to invite user to unknown organization." -msgstr "Impossible d'inviter un utilisateur à une organisation inconnue." - -#: src/affiliation/mutations/invite-user-to-org.js:170 -#: src/affiliation/mutations/invite-user-to-org.js:185 -msgid "Unable to invite user. Please try again." -msgstr "Impossible d'inviter un utilisateur. Veuillez réessayer." - -#: src/affiliation/mutations/invite-user-to-org.js:73 -msgid "Unable to invite yourself to an org." -msgstr "Impossible de s'inviter à un org." - -#: src/affiliation/mutations/leave-organization.js:51 -msgid "Unable to leave undefined organization." -msgstr "Impossible de quitter une organisation non définie." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:254 -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:266 -msgid "Unable to load Aggregate guidance tag(s). Please try again." -msgstr "Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:141 -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:153 -msgid "Unable to load DKIM failure data. Please try again." -msgstr "Impossible de charger les données d'échec DKIM. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:272 -msgid "Unable to load DKIM guidance tag(s). Please try again." -msgstr "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:258 -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:270 -msgid "Unable to load DKIM result(s). Please try again." -msgstr "Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:279 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:289 -msgid "Unable to load DKIM scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:153 -msgid "Unable to load DMARC failure data. Please try again." -msgstr "Impossible de charger les données d'échec DMARC. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:272 -msgid "Unable to load DMARC guidance tag(s). Please try again." -msgstr "Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:319 -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:331 -msgid "Unable to load DMARC scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:472 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:484 -#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:20 -#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:32 -#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:20 -#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:32 -msgid "Unable to load DMARC summary data. Please try again." -msgstr "Impossible de charger les données de synthèse DMARC. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:272 -msgid "Unable to load HTTPS guidance tag(s). Please try again." -msgstr "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer." - -#: src/web-scan/loaders/load-https-by-key.js:33 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:333 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:345 -msgid "Unable to load HTTPS scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:140 -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:152 -msgid "Unable to load SPF failure data. Please try again." -msgstr "Impossible de charger les données d'échec SPF. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:272 -msgid "Unable to load SPF guidance tag(s). Please try again." -msgstr "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:306 -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:316 -msgid "Unable to load SPF scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) SPF. Veuillez réessayer." - -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 -msgid "Unable to load SSL guidance tag(s). Please try again." -msgstr "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:380 -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:390 -msgid "Unable to load SSL scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) SSL. Veuillez réessayer." - -#: src/auth/check-user-belongs-to-org.js:22 -msgid "Unable to load affiliation information. Please try again." -msgstr "Impossible de charger les informations d'affiliation. Veuillez réessayer." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:266 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:449 -msgid "Unable to load affiliation(s). Please try again." -msgstr "Impossible de charger l'affiliation (s). Veuillez réessayer." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:364 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:374 -#: src/domain/loaders/load-domain-connections-by-user-id.js:394 -msgid "Unable to load domain(s). Please try again." -msgstr "Impossible de charger le(s) domaine(s). Veuillez réessayer." - -#: src/domain/loaders/load-domain-by-domain.js:19 -#: src/domain/loaders/load-domain-by-domain.js:31 -#: src/domain/loaders/load-domain-by-key.js:19 -#: src/domain/loaders/load-domain-by-key.js:31 -msgid "Unable to load domain. Please try again." -msgstr "Impossible de charger le domaine. Veuillez réessayer." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:13 -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:140 -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:152 -msgid "Unable to load full pass data. Please try again." -msgstr "Impossible de charger les données complètes de la passe. Veuillez réessayer." - -#: src/summaries/queries/mail-summary.js:12 -msgid "Unable to load mail summary. Please try again." -msgstr "Impossible de charger le résumé du courrier. Veuillez réessayer." - -#: src/organization/loaders/load-organization-by-key.js:33 -#: src/organization/loaders/load-organization-by-key.js:47 -#: src/organization/loaders/load-organization-by-slug.js:36 -#: src/organization/loaders/load-organization-by-slug.js:51 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:513 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:525 -#: src/organization/loaders/load-organization-connections-by-user-id.js:504 -#: src/organization/loaders/load-organization-connections-by-user-id.js:516 -msgid "Unable to load organization(s). Please try again." -msgstr "Impossible de charger l'organisation (s). Veuillez réessayer." - -#: src/auth/check-org-owner.js:22 -#: src/auth/check-org-owner.js:34 -msgid "Unable to load owner information. Please try again." -msgstr "Impossible de charger les informations sur le propriétaire. Veuillez réessayer." - -#: src/summaries/loaders/load-chart-summary-by-key.js:19 -#: src/summaries/loaders/load-chart-summary-by-key.js:31 -msgid "Unable to load summary. Please try again." -msgstr "Impossible de charger le résumé. Veuillez réessayer." - -#: src/user/loaders/load-user-by-key.js:19 -#: src/user/loaders/load-user-by-key.js:31 -#: src/user/loaders/load-user-by-username.js:19 -#: src/user/loaders/load-user-by-username.js:31 -msgid "Unable to load user(s). Please try again." -msgstr "Impossible de charger le(s) utilisateur(s). Veuillez réessayer." - -#: src/verified-domains/loaders/load-verified-domain-by-domain.js:24 -#: src/verified-domains/loaders/load-verified-domain-by-domain.js:38 -#: src/verified-domains/loaders/load-verified-domain-by-key.js:24 -#: src/verified-domains/loaders/load-verified-domain-by-key.js:38 -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:306 -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:318 -#: src/verified-domains/loaders/load-verified-domain-connections.js:312 -#: src/verified-domains/loaders/load-verified-domain-connections.js:324 -msgid "Unable to load verified domain(s). Please try again." -msgstr "Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:423 -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:435 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:422 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:434 -msgid "Unable to load verified organization(s). Please try again." -msgstr "Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer." - -#: src/summaries/queries/web-summary.js:13 -msgid "Unable to load web summary. Please try again." -msgstr "Impossible de charger le résumé web. Veuillez réessayer." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:254 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:437 -msgid "Unable to query affiliation(s). Please try again." -msgstr "Impossible de demander l'affiliation (s). Veuillez réessayer." - -#: src/domain/loaders/load-domain-connections-by-user-id.js:384 -msgid "Unable to query domain(s). Please try again." -msgstr "Impossible d'interroger le(s) domaine(s). Veuillez réessayer." - -#: src/user/mutations/refresh-tokens.js:49 -#: src/user/mutations/refresh-tokens.js:63 -#: src/user/mutations/refresh-tokens.js:78 -#: src/user/mutations/refresh-tokens.js:93 -#: src/user/mutations/refresh-tokens.js:105 -#: src/user/mutations/refresh-tokens.js:142 -#: src/user/mutations/refresh-tokens.js:151 -msgid "Unable to refresh tokens, please sign in." -msgstr "Impossible de rafraîchir les jetons, veuillez vous connecter." - -#: src/affiliation/mutations/remove-user-from-org.js:122 -msgid "Unable to remove a user that already does not belong to this organization." -msgstr "Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation." - -#: src/domain/mutations/remove-domain.js:79 -msgid "Unable to remove domain from unknown organization." -msgstr "Impossible de supprimer le domaine d'une organisation inconnue." - -#: src/domain/mutations/remove-domain.js:125 -#: src/domain/mutations/remove-domain.js:139 -#: src/domain/mutations/remove-domain.js:178 -#: src/domain/mutations/remove-domain.js:197 -#: src/domain/mutations/remove-domain.js:332 -#: src/domain/mutations/remove-domain.js:359 -#: src/domain/mutations/remove-domain.js:382 -#: src/domain/mutations/remove-domain.js:393 -msgid "Unable to remove domain. Please try again." -msgstr "Impossible de supprimer le domaine. Veuillez réessayer." - -#: src/organization/mutations/remove-organization.js:112 -#: src/organization/mutations/remove-organization.js:124 -#: src/organization/mutations/remove-organization.js:156 -#: src/organization/mutations/remove-organization.js:173 -#: src/organization/mutations/remove-organization.js:205 -#: src/organization/mutations/remove-organization.js:217 -#: src/organization/mutations/remove-organization.js:256 -#: src/organization/mutations/remove-organization.js:378 -#: src/organization/mutations/remove-organization.js:411 -#: src/organization/mutations/remove-organization.js:449 -#: src/organization/mutations/remove-organization.js:460 -msgid "Unable to remove organization. Please try again." -msgstr "Impossible de supprimer l'organisation. Veuillez réessayer." - -#: src/user/mutations/remove-phone-number.js:63 -#: src/user/mutations/remove-phone-number.js:74 -msgid "Unable to remove phone number. Please try again." -msgstr "Impossible de supprimer le numéro de téléphone. Veuillez réessayer." - -#: src/domain/mutations/remove-domain.js:63 -msgid "Unable to remove unknown domain." -msgstr "Impossible de supprimer un domaine inconnu." - -#: src/organization/mutations/remove-organization.js:56 -msgid "Unable to remove unknown organization." -msgstr "Impossible de supprimer une organisation inconnue." - -#: src/affiliation/mutations/remove-user-from-org.js:89 -msgid "Unable to remove unknown user from organization." -msgstr "Impossible de supprimer un utilisateur inconnu de l'organisation." - -#: src/affiliation/mutations/remove-user-from-org.js:75 -msgid "Unable to remove user from organization." -msgstr "Impossible de supprimer un utilisateur de l'organisation." - -#: src/affiliation/mutations/remove-user-from-org.js:109 -#: src/affiliation/mutations/remove-user-from-org.js:136 -#: src/affiliation/mutations/remove-user-from-org.js:179 -#: src/affiliation/mutations/remove-user-from-org.js:190 -msgid "Unable to remove user from this organization. Please try again." -msgstr "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer." - -#: src/affiliation/mutations/remove-user-from-org.js:61 -msgid "Unable to remove user from unknown organization." -msgstr "Impossible de supprimer un utilisateur d'une organisation inconnue." - -#: src/domain/mutations/request-scan.js:57 -msgid "Unable to request a one time scan on an unknown domain." -msgstr "Impossible de demander un scan unique sur un domaine inconnu." - -#: src/user/mutations/reset-password.js:95 -msgid "Unable to reset password. Please request a new email." -msgstr "Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail." - -#: src/user/mutations/reset-password.js:82 -#: src/user/mutations/reset-password.js:152 -#: src/user/mutations/reset-password.js:161 -msgid "Unable to reset password. Please try again." -msgstr "Impossible de réinitialiser le mot de passe. Veuillez réessayer." - -#: src/domain/objects/domain.js:160 -#: src/domain/objects/domain.js:203 -msgid "Unable to retrieve DMARC report information for: {domain}" -msgstr "Impossible de récupérer les informations du rapport DMARC pour : {domain}" - -#: src/dmarc-summaries/loaders/load-start-date-from-period.js:33 -#: src/dmarc-summaries/loaders/load-start-date-from-period.js:48 -msgid "Unable to select DMARC report(s) for this period and year." -msgstr "Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année." - -#: src/notify/notify-send-authenticate-email.js:21 -msgid "Unable to send authentication email. Please try again." -msgstr "Impossible d'envoyer l'email d'authentification. Veuillez réessayer." - -#: src/notify/notify-send-authenticate-text-msg.js:35 -msgid "Unable to send authentication text message. Please try again." -msgstr "Impossible d'envoyer un message texte d'authentification. Veuillez réessayer." - -#: src/notify/notify-send-org-invite-create-account.js:29 -#: src/notify/notify-send-org-invite-email.js:25 -msgid "Unable to send org invite email. Please try again." -msgstr "Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer." - -#: src/notify/notify-send-password-reset-email.js:27 -msgid "Unable to send password reset email. Please try again." -msgstr "Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer." - -#: src/notify/notify-send-tfa-text-msg.js:27 -msgid "Unable to send two factor authentication message. Please try again." -msgstr "Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer." - -#: src/notify/notify-send-verification-email.js:29 -msgid "Unable to send verification email. Please try again." -msgstr "Impossible d'envoyer l'email de vérification. Veuillez réessayer." - -#: src/user/mutations/set-phone-number.js:108 -#: src/user/mutations/set-phone-number.js:117 -msgid "Unable to set phone number, please try again." -msgstr "Impossible de définir le numéro de téléphone, veuillez réessayer." - -#: src/user/mutations/sign-in.js:112 -#: src/user/mutations/sign-in.js:149 -#: src/user/mutations/sign-in.js:158 -#: src/user/mutations/sign-in.js:204 -#: src/user/mutations/sign-in.js:213 -#: src/user/mutations/sign-in.js:276 -#: src/user/mutations/sign-in.js:285 -msgid "Unable to sign in, please try again." -msgstr "Impossible de se connecter, veuillez réessayer." - -#: src/user/mutations/sign-up.js:204 -#: src/user/mutations/sign-up.js:218 -msgid "Unable to sign up, please contact org admin for a new invite." -msgstr "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation." - -#: src/user/mutations/sign-up.js:172 -#: src/user/mutations/sign-up.js:182 -#: src/user/mutations/sign-up.js:240 -#: src/user/mutations/sign-up.js:250 -msgid "Unable to sign up. Please try again." -msgstr "Impossible de s'inscrire. Veuillez réessayer." - -#: src/affiliation/mutations/transfer-org-ownership.js:131 -#: src/affiliation/mutations/transfer-org-ownership.js:178 -#: src/affiliation/mutations/transfer-org-ownership.js:202 -#: src/affiliation/mutations/transfer-org-ownership.js:214 -msgid "Unable to transfer organization ownership. Please try again." -msgstr "Impossible de transférer la propriété de l'organisation. Veuillez réessayer." - -#: src/affiliation/mutations/transfer-org-ownership.js:78 -msgid "Unable to transfer ownership of a verified organization." -msgstr "Impossible de transférer la propriété d'une organisation vérifiée." - -#: src/affiliation/mutations/transfer-org-ownership.js:112 -msgid "Unable to transfer ownership of an org to an undefined user." -msgstr "Impossible de transférer la propriété d'un org à un utilisateur non défini." - -#: src/affiliation/mutations/transfer-org-ownership.js:65 -msgid "Unable to transfer ownership of undefined organization." -msgstr "Impossible de transférer la propriété d'une organisation non définie." - -#: src/affiliation/mutations/transfer-org-ownership.js:144 -msgid "Unable to transfer ownership to a user outside the org. Please invite the user and try again." -msgstr "Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer." - -#: src/user/mutations/verify-phone-number.js:92 -#: src/user/mutations/verify-phone-number.js:103 -msgid "Unable to two factor authenticate. Please try again." -msgstr "Impossible de s'authentifier par deux facteurs. Veuillez réessayer." - -#: src/domain/mutations/update-domain.js:93 -msgid "Unable to update domain in an unknown org." -msgstr "Impossible de mettre à jour le domaine dans un org inconnu." - -#: src/domain/mutations/update-domain.js:141 -msgid "Unable to update domain that does not belong to the given organization." -msgstr "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée." - -#: src/domain/mutations/update-domain.js:130 -#: src/domain/mutations/update-domain.js:177 -#: src/domain/mutations/update-domain.js:187 -msgid "Unable to update domain. Please try again." -msgstr "Impossible de mettre à jour le domaine. Veuillez réessayer." - -#: src/organization/mutations/update-organization.js:176 -#: src/organization/mutations/update-organization.js:208 -#: src/organization/mutations/update-organization.js:220 -#: src/organization/mutations/update-organization.js:275 -#: src/organization/mutations/update-organization.js:286 -msgid "Unable to update organization. Please try again." -msgstr "Impossible de mettre à jour l'organisation. Veuillez réessayer." - -#: src/user/mutations/update-user-password.js:62 -msgid "Unable to update password, current password does not match. Please try again." -msgstr "Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer." - -#: src/user/mutations/update-user-password.js:76 -msgid "Unable to update password, new passwords do not match. Please try again." -msgstr "Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer." - -#: src/user/mutations/update-user-password.js:90 -msgid "Unable to update password, passwords do not match requirements. Please try again." -msgstr "Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer." - -#: src/user/mutations/update-user-password.js:119 -#: src/user/mutations/update-user-password.js:128 -msgid "Unable to update password. Please try again." -msgstr "Impossible de mettre à jour le mot de passe. Veuillez réessayer." - -#: src/user/mutations/update-user-profile.js:134 -#: src/user/mutations/update-user-profile.js:143 -msgid "Unable to update profile. Please try again." -msgstr "Impossible de mettre à jour le profil. Veuillez réessayer." - -#: src/affiliation/mutations/update-user-role.js:97 -msgid "Unable to update role: organization unknown." -msgstr "Impossible de mettre à jour le rôle : organisation inconnue." - -#: src/affiliation/mutations/update-user-role.js:143 -msgid "Unable to update role: user does not belong to organization." -msgstr "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation." - -#: src/affiliation/mutations/update-user-role.js:83 -msgid "Unable to update role: user unknown." -msgstr "Impossible de mettre à jour le rôle : utilisateur inconnu." - -#: src/domain/mutations/update-domain.js:79 -msgid "Unable to update unknown domain." -msgstr "Impossible de mettre à jour un domaine inconnu." - -#: src/organization/mutations/update-organization.js:141 -msgid "Unable to update unknown organization." -msgstr "Impossible de mettre à jour une organisation inconnue." - -#: src/affiliation/mutations/update-user-role.js:131 -#: src/affiliation/mutations/update-user-role.js:156 -#: src/affiliation/mutations/update-user-role.js:251 -#: src/affiliation/mutations/update-user-role.js:262 -msgid "Unable to update user's role. Please try again." -msgstr "Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer." - -#: src/affiliation/mutations/update-user-role.js:69 -msgid "Unable to update your own role." -msgstr "Impossible de mettre à jour votre propre rôle." - -#: src/user/mutations/verify-account.js:52 -#: src/user/mutations/verify-account.js:70 -msgid "Unable to verify account. Please request a new email." -msgstr "Impossible de vérifier le compte. Veuillez demander un nouvel e-mail." - -#: src/user/mutations/verify-account.js:99 -#: src/user/mutations/verify-account.js:108 -msgid "Unable to verify account. Please try again." -msgstr "Impossible de vérifier le compte. Veuillez réessayer." - -#: src/user/queries/is-user-super-admin.js:23 -msgid "Unable to verify if user is a super admin, please try again." -msgstr "Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer." - -#: src/user/queries/is-user-admin.js:58 -msgid "Unable to verify if user is an admin, please try again." -msgstr "Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer." - -#: src/organization/mutations/verify-organization.js:115 -#: src/organization/mutations/verify-organization.js:137 -#: src/organization/mutations/verify-organization.js:148 -msgid "Unable to verify organization. Please try again." -msgstr "Impossible de vérifier l'organisation. Veuillez réessayer." - -#: src/organization/mutations/verify-organization.js:54 -msgid "Unable to verify unknown organization." -msgstr "Impossible de vérifier une organisation inconnue." - -#: src/user/queries/find-user-by-username.js:41 -msgid "User could not be queried." -msgstr "L'utilisateur n'a pas pu être interrogé." - -#: src/affiliation/mutations/update-user-role.js:272 -msgid "User role was updated successfully." -msgstr "Le rôle de l'utilisateur a été mis à jour avec succès." - -#: src/user/mutations/update-user-profile.js:75 -msgid "Username not available, please try another." -msgstr "Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre." - -#: src/auth/verified-required.js:15 -msgid "Verification error. Please verify your account via email to access content." -msgstr "Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:71 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:161 -msgid "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:89 -msgid "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:114 -msgid "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:138 -msgid "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:184 -msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:138 -#: src/domain/loaders/load-domain-connections-by-user-id.js:147 -msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:85 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:89 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:89 -msgid "You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:147 -msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:178 -#: src/organization/loaders/load-organization-connections-by-user-id.js:177 -msgid "You must provide a `first` or `last` value to properly paginate the `Organization` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:133 -msgid "You must provide a `first` or `last` value to properly paginate the `SPF` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:171 -msgid "You must provide a `first` or `last` value to properly paginate the `SSL` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:36 -msgid "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:117 -#: src/verified-domains/loaders/load-verified-domain-connections.js:117 -msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:167 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:165 -msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:26 -msgid "You must provide a `period` value to access the `DmarcSummaries` connection." -msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:38 -msgid "You must provide a `year` value to access the `DmarcSummaries` connection." -msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:118 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:231 -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:185 -#: src/domain/loaders/load-domain-connections-by-user-id.js:194 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:170 -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:136 -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:186 -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:180 -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:136 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:136 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:225 -#: src/organization/loaders/load-organization-connections-by-user-id.js:224 -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:164 -#: src/verified-domains/loaders/load-verified-domain-connections.js:164 -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:214 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:212 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:194 -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:218 -msgid "`{argSet}` must be of type `number` not `{typeSet}`." -msgstr "`{argSet}` doit être de type `number` et non `{typeSet}`." - -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:92 -#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:182 -msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `Affiliation` ne peut être inférieur à zéro." - -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:110 -msgid "`{argSet}` on the `DKIMResults` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DKIMResults` ne peut être inférieur à zéro." - -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:135 -msgid "`{argSet}` on the `DKIM` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DKIM` ne peut être inférieur à zéro." - -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:160 -msgid "`{argSet}` on the `DMARC` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DMARC` ne peut être inférieur à zéro." - -#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro." - -#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro." - -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:205 -msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro." - -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:159 -#: src/domain/loaders/load-domain-connections-by-user-id.js:168 -msgid "`{argSet}` on the `Domain` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `Domain` ne peut être inférieur à zéro." - -#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `FullPassTable` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `FullPassTable` ne peut être inférieur à zéro." - -#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:106 -#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:110 -#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:110 -msgid "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `GuidanceTag` ne peut être inférieure à zéro." - -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:168 -msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `HTTPS` ne peut être inférieur à zéro." - -#: src/organization/loaders/load-organization-connections-by-domain-id.js:199 -#: src/organization/loaders/load-organization-connections-by-user-id.js:198 -msgid "`{argSet}` on the `Organization` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `Organization` ne peut être inférieure à zéro." - -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:154 -msgid "`{argSet}` on the `SPF` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `SPF` ne peut être inférieure à zéro." - -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:192 -msgid "`{argSet}` on the `SSL` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `SSL` ne peut être inférieur à zéro." - -#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:57 -msgid "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro." - -#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:138 -#: src/verified-domains/loaders/load-verified-domain-connections.js:138 -msgid "`{argSet}` on the `VerifiedDomain` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro." - -#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:188 -#: src/verified-organizations/loaders/load-verified-organizations-connections.js:186 -msgid "`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro." diff --git a/api-js/src/node.js b/api-js/src/node.js deleted file mode 100644 index 236f3c9d75..0000000000 --- a/api-js/src/node.js +++ /dev/null @@ -1,11 +0,0 @@ -import { nodeDefinitions } from 'graphql-relay' - -export const { nodeField, nodesField, nodeInterface } = nodeDefinitions( - (_globalId) => {}, - (object) => { - switch (object) { - default: - return null - } - }, -) diff --git a/api-js/src/notify/__tests__/notify-send-org-invite-email.test.js b/api-js/src/notify/__tests__/notify-send-org-invite-email.test.js deleted file mode 100644 index 13ddd5f129..0000000000 --- a/api-js/src/notify/__tests__/notify-send-org-invite-email.test.js +++ /dev/null @@ -1,205 +0,0 @@ -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import { sendOrgInviteEmail } from '../index' - -const { NOTIFICATION_ORG_INVITE_EN, NOTIFICATION_ORG_INVITE_FR } = process.env - -describe('given the sendOrgInviteEmail function', () => { - let i18n - let consoleOutput = [] - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(async () => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(async () => { - consoleOutput = [] - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('email successfully sent', () => { - it('returns nothing', async () => { - const sendEmail = jest.fn() - const notifyClient = { - sendEmail, - } - - const user = { - userName: 'test@email.ca', - displayName: 'Test Account', - preferredLang: 'english', - } - - const mockedSendOrgInviteEmail = sendOrgInviteEmail({ - notifyClient, - i18n, - }) - await mockedSendOrgInviteEmail({ - user, - orgName: 'Test Org', - }) - - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_EN, - user.userName, - { - personalisation: { - display_name: user.displayName, - organization_name: 'Test Org', - }, - }, - ) - }) - }) - describe('an error occurs while sending email', () => { - it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) - const notifyClient = { - sendEmail, - } - - const user = { - userName: 'test@email.ca', - displayName: 'Test Account', - preferredLang: 'english', - } - - try { - const mockedSendOrgInviteEmail = sendOrgInviteEmail({ - notifyClient, - i18n, - }) - await mockedSendOrgInviteEmail({ - user, - orgName: 'Test Org', - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to send org invite email. Please try again.'), - ) - } - - expect(consoleOutput).toEqual([ - `Error occurred when sending org invite email for ${user._key}: Error: Notification error occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('email successfully sent', () => { - it('returns nothing', async () => { - const sendEmail = jest.fn() - const notifyClient = { - sendEmail, - } - - const user = { - userName: 'test@email.ca', - displayName: 'Test Account', - preferredLang: 'french', - } - - const mockedSendOrgInviteEmail = sendOrgInviteEmail({ - notifyClient, - i18n, - }) - await mockedSendOrgInviteEmail({ - user, - orgName: 'Test Org', - }) - - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_FR, - user.userName, - { - personalisation: { - display_name: user.displayName, - organization_name: 'Test Org', - }, - }, - ) - }) - }) - describe('an error occurs while sending email', () => { - it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) - const notifyClient = { - sendEmail, - } - - const user = { - userName: 'test@email.ca', - displayName: 'Test Account', - preferredLang: 'french', - } - - try { - const mockedSendOrgInviteEmail = sendOrgInviteEmail({ - notifyClient, - i18n, - }) - await mockedSendOrgInviteEmail({ - user, - orgName: 'Test Org', - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `Error occurred when sending org invite email for ${user._key}: Error: Notification error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/notify/__tests__/notify-send-tfa-text-msg.test.js b/api-js/src/notify/__tests__/notify-send-tfa-text-msg.test.js deleted file mode 100644 index be27ec56aa..0000000000 --- a/api-js/src/notify/__tests__/notify-send-tfa-text-msg.test.js +++ /dev/null @@ -1,192 +0,0 @@ -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import { sendTfaTextMsg } from '../index' - -const { - NOTIFICATION_TWO_FACTOR_CODE_EN, - NOTIFICATION_TWO_FACTOR_CODE_FR, -} = process.env - -describe('given the sendTfaTextMsg function', () => { - let i18n - let consoleOutput = [] - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(async () => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(async () => { - consoleOutput = [] - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('text message successfully sent', () => { - it('returns nothing', async () => { - const sendSms = jest.fn() - const notifyClient = { - sendSms, - } - const user = { - phoneNumber: '+12345678901', - tfaCode: 123456, - preferredLang: 'english', - } - - const mockedSendTfaTextMsg = sendTfaTextMsg({ notifyClient, i18n }) - await mockedSendTfaTextMsg({ - phoneNumber: user.phoneNumber, - user, - }) - - expect(notifyClient.sendSms).toHaveBeenCalledWith( - NOTIFICATION_TWO_FACTOR_CODE_EN, - user.phoneNumber, - { - personalisation: { - verify_code: user.tfaCode, - }, - }, - ) - }) - }) - describe('an error occurs while sending text message', () => { - it('throws an error message', async () => { - const sendSms = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) - const notifyClient = { - sendSms, - } - const user = { - phoneNumber: '+12345678901', - tfaCode: 123456, - preferredLang: 'english', - } - - try { - const mockedSendTfaTextMsg = sendTfaTextMsg({ notifyClient, i18n }) - await mockedSendTfaTextMsg({ - phoneNumber: user.phoneNumber, - user, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to send two factor authentication message. Please try again.', - ), - ) - } - - expect(consoleOutput).toEqual([ - `Error occurred when sending two factor authentication message for ${user._key}: Error: Notification error occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('text message successfully sent', () => { - it('returns nothing', async () => { - const sendSms = jest.fn() - const notifyClient = { - sendSms, - } - const user = { - phoneNumber: '+12345678901', - tfaCode: 123456, - preferredLang: 'french', - } - - const mockedSendTfaTextMsg = sendTfaTextMsg({ notifyClient, i18n }) - await mockedSendTfaTextMsg({ - phoneNumber: user.phoneNumber, - user, - }) - - expect(notifyClient.sendSms).toHaveBeenCalledWith( - NOTIFICATION_TWO_FACTOR_CODE_FR, - user.phoneNumber, - { - personalisation: { - verify_code: user.tfaCode, - }, - }, - ) - }) - }) - describe('an error occurs while sending text message', () => { - it('throws an error message', async () => { - const sendSms = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) - const notifyClient = { - sendSms, - } - const user = { - phoneNumber: '+12345678901', - tfaCode: 123456, - preferredLang: 'french', - } - - try { - const mockedSendTfaTextMsg = sendTfaTextMsg({ notifyClient, i18n }) - await mockedSendTfaTextMsg({ - phoneNumber: user.phoneNumber, - user, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `Error occurred when sending two factor authentication message for ${user._key}: Error: Notification error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/notify/__tests__/notify-send-verification-email.test.js b/api-js/src/notify/__tests__/notify-send-verification-email.test.js deleted file mode 100644 index 3d7522bf9f..0000000000 --- a/api-js/src/notify/__tests__/notify-send-verification-email.test.js +++ /dev/null @@ -1,204 +0,0 @@ -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import { sendVerificationEmail } from '../index' - -const { - NOTIFICATION_VERIFICATION_EMAIL_EN, - NOTIFICATION_VERIFICATION_EMAIL_FR, -} = process.env - -describe('given the sendVerificationEmail function', () => { - let i18n - let consoleOutput = [] - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(async () => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(async () => { - consoleOutput = [] - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('email successfully sent', () => { - it('returns nothing', async () => { - const sendEmail = jest.fn() - const notifyClient = { - sendEmail, - } - const user = { - userName: 'test.email@email.ca', - displayName: 'Test Account', - preferredLang: 'english', - } - - const mockedSendVerificationEmail = sendVerificationEmail({ - notifyClient, - i18n, - }) - await mockedSendVerificationEmail({ - verifyUrl: 'verify.url', - user, - }) - - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_VERIFICATION_EMAIL_EN, - user.userName, - { - personalisation: { - user: user.displayName, - verify_email_url: 'verify.url', - }, - }, - ) - }) - }) - describe('an error occurs while sending email', () => { - it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) - const notifyClient = { - sendEmail, - } - const user = { - userName: 'test.email@email.ca', - displayName: 'Test Account', - preferredLang: 'english', - } - - try { - const mockedSendVerificationEmail = sendVerificationEmail({ - notifyClient, - i18n, - }) - await mockedSendVerificationEmail({ - verifyUrl: 'verify.url', - user, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to send verification email. Please try again.'), - ) - } - - expect(consoleOutput).toEqual([ - `Error occurred when sending verification email for ${user._key}: Error: Notification error occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('email successfully sent', () => { - it('returns nothing', async () => { - const sendEmail = jest.fn() - const notifyClient = { - sendEmail, - } - const user = { - userName: 'test.email@email.ca', - displayName: 'Test Account', - preferredLang: 'french', - } - - const mockedSendVerificationEmail = sendVerificationEmail({ - notifyClient, - i18n, - }) - await mockedSendVerificationEmail({ - verifyUrl: 'verify.url', - user, - }) - - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_VERIFICATION_EMAIL_FR, - user.userName, - { - personalisation: { - user: user.displayName, - verify_email_url: 'verify.url', - }, - }, - ) - }) - }) - describe('an error occurs while sending email', () => { - it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) - const notifyClient = { - sendEmail, - } - const user = { - userName: 'test.email@email.ca', - displayName: 'Test Account', - preferredLang: 'french', - } - - try { - const mockedSendVerificationEmail = sendVerificationEmail({ - notifyClient, - i18n, - }) - await mockedSendVerificationEmail({ - verifyUrl: 'verify.url', - user, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Impossible d'envoyer l'email de vérification. Veuillez réessayer.", - ), - ) - } - - expect(consoleOutput).toEqual([ - `Error occurred when sending verification email for ${user._key}: Error: Notification error occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/notify/index.js b/api-js/src/notify/index.js deleted file mode 100644 index fc86b60b02..0000000000 --- a/api-js/src/notify/index.js +++ /dev/null @@ -1,8 +0,0 @@ -export * from './notify-client' -export * from './notify-send-authenticate-email' -export * from './notify-send-authenticate-text-msg' -export * from './notify-send-org-invite-create-account' -export * from './notify-send-org-invite-email' -export * from './notify-send-password-reset-email' -export * from './notify-send-tfa-text-msg' -export * from './notify-send-verification-email' diff --git a/api-js/src/notify/notify-client.js b/api-js/src/notify/notify-client.js deleted file mode 100644 index 098ca95800..0000000000 --- a/api-js/src/notify/notify-client.js +++ /dev/null @@ -1,8 +0,0 @@ -import { NotifyClient } from 'notifications-node-client' - -const { NOTIFICATION_API_KEY, NOTIFICATION_API_URL } = process.env - -export const notifyClient = new NotifyClient( - NOTIFICATION_API_URL, - NOTIFICATION_API_KEY, -) diff --git a/api-js/src/notify/notify-send-authenticate-email.js b/api-js/src/notify/notify-send-authenticate-email.js deleted file mode 100644 index 2973cf314e..0000000000 --- a/api-js/src/notify/notify-send-authenticate-email.js +++ /dev/null @@ -1,22 +0,0 @@ -import { t } from '@lingui/macro' - -const { NOTIFICATION_AUTHENTICATE_EMAIL_ID } = process.env - -export const sendAuthEmail = ({ notifyClient, i18n }) => async ({ user }) => { - const templateId = NOTIFICATION_AUTHENTICATE_EMAIL_ID - try { - await notifyClient.sendEmail(templateId, user.userName, { - personalisation: { - user: user.displayName, - tfa_code: user.tfaCode, - }, - }) - } catch (err) { - console.error( - `Error occurred when sending authentication code via email for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send authentication email. Please try again.`), - ) - } -} diff --git a/api-js/src/notify/notify-send-authenticate-text-msg.js b/api-js/src/notify/notify-send-authenticate-text-msg.js deleted file mode 100644 index c476efd3bc..0000000000 --- a/api-js/src/notify/notify-send-authenticate-text-msg.js +++ /dev/null @@ -1,35 +0,0 @@ -import crypto from 'crypto' -import { t } from '@lingui/macro' - -const { CIPHER_KEY, NOTIFICATION_AUTHENTICATE_TEXT_ID } = process.env - -export const sendAuthTextMsg = ({ notifyClient, i18n }) => async ({ user }) => { - const templateId = NOTIFICATION_AUTHENTICATE_TEXT_ID - - const { iv, tag, phoneNumber: encryptedData } = user.phoneDetails - const decipher = crypto.createDecipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(iv, 'hex'), - { authTagLength: 16 }, - ) - decipher.setAuthTag(Buffer.from(tag, 'hex')) - let phoneNumber = decipher.update(encryptedData, 'hex', 'utf8') - phoneNumber += decipher.final('utf8') - - try { - await notifyClient.sendSms(templateId, phoneNumber, { - personalisation: { - tfa_code: user.tfaCode, - }, - }) - return true - } catch (err) { - console.error( - `Error occurred when sending authentication code via text for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send authentication text message. Please try again.`), - ) - } -} diff --git a/api-js/src/notify/notify-send-org-invite-create-account.js b/api-js/src/notify/notify-send-org-invite-create-account.js deleted file mode 100644 index 40c7361240..0000000000 --- a/api-js/src/notify/notify-send-org-invite-create-account.js +++ /dev/null @@ -1,34 +0,0 @@ -import { t } from '@lingui/macro' - -const { - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, -} = process.env - -export const sendOrgInviteCreateAccount = ({ notifyClient, i18n }) => async ({ - user, - orgName, - createAccountLink, -}) => { - let templateId = NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN - if (user.preferredLang === 'french') { - templateId = NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR - } - - try { - await notifyClient.sendEmail(templateId, user.userName, { - personalisation: { - create_account_link: createAccountLink, - display_name: user.userName, - organization_name: orgName, - }, - }) - } catch (err) { - console.error( - `Error occurred when sending org create account invite email for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send org invite email. Please try again.`), - ) - } -} diff --git a/api-js/src/notify/notify-send-org-invite-email.js b/api-js/src/notify/notify-send-org-invite-email.js deleted file mode 100644 index 3f68106fce..0000000000 --- a/api-js/src/notify/notify-send-org-invite-email.js +++ /dev/null @@ -1,29 +0,0 @@ -import { t } from '@lingui/macro' - -const { NOTIFICATION_ORG_INVITE_EN, NOTIFICATION_ORG_INVITE_FR } = process.env - -export const sendOrgInviteEmail = ({ notifyClient, i18n }) => async ({ - user, - orgName, -}) => { - let templateId = NOTIFICATION_ORG_INVITE_EN - if (user.preferredLang === 'french') { - templateId = NOTIFICATION_ORG_INVITE_FR - } - - try { - await notifyClient.sendEmail(templateId, user.userName, { - personalisation: { - display_name: user.displayName, - organization_name: orgName, - }, - }) - } catch (err) { - console.error( - `Error occurred when sending org invite email for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send org invite email. Please try again.`), - ) - } -} diff --git a/api-js/src/notify/notify-send-password-reset-email.js b/api-js/src/notify/notify-send-password-reset-email.js deleted file mode 100644 index b08727593d..0000000000 --- a/api-js/src/notify/notify-send-password-reset-email.js +++ /dev/null @@ -1,33 +0,0 @@ -import { t } from '@lingui/macro' - -const { - NOTIFICATION_PASSWORD_RESET_EN, - NOTIFICATION_PASSWORD_RESET_FR, -} = process.env - -export const sendPasswordResetEmail = ({ notifyClient, i18n }) => async ({ - user, - resetUrl, -}) => { - let templateId = NOTIFICATION_PASSWORD_RESET_EN - if (user.preferredLang === 'french') { - templateId = NOTIFICATION_PASSWORD_RESET_FR - } - - try { - await notifyClient.sendEmail(templateId, user.userName, { - personalisation: { - user: user.displayName, - password_reset_url: resetUrl, - }, - }) - return true - } catch (err) { - console.error( - `Error occurred when sending password reset email for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send password reset email. Please try again.`), - ) - } -} diff --git a/api-js/src/notify/notify-send-tfa-text-msg.js b/api-js/src/notify/notify-send-tfa-text-msg.js deleted file mode 100644 index 75013b3dca..0000000000 --- a/api-js/src/notify/notify-send-tfa-text-msg.js +++ /dev/null @@ -1,34 +0,0 @@ -import { t } from '@lingui/macro' - -const { - NOTIFICATION_TWO_FACTOR_CODE_EN, - NOTIFICATION_TWO_FACTOR_CODE_FR, -} = process.env - -export const sendTfaTextMsg = ({ notifyClient, i18n }) => async ({ - phoneNumber, - user, -}) => { - let templateId = NOTIFICATION_TWO_FACTOR_CODE_EN - if (user.preferredLang === 'french') { - templateId = NOTIFICATION_TWO_FACTOR_CODE_FR - } - - try { - await notifyClient.sendSms(templateId, phoneNumber, { - personalisation: { - verify_code: user.tfaCode, - }, - }) - return true - } catch (err) { - console.error( - `Error occurred when sending two factor authentication message for ${user._key}: ${err}`, - ) - throw new Error( - i18n._( - t`Unable to send two factor authentication message. Please try again.`, - ), - ) - } -} diff --git a/api-js/src/notify/notify-send-verification-email.js b/api-js/src/notify/notify-send-verification-email.js deleted file mode 100644 index cba13bd00d..0000000000 --- a/api-js/src/notify/notify-send-verification-email.js +++ /dev/null @@ -1,33 +0,0 @@ -import { t } from '@lingui/macro' - -const { - NOTIFICATION_VERIFICATION_EMAIL_EN, - NOTIFICATION_VERIFICATION_EMAIL_FR, -} = process.env - -export const sendVerificationEmail = ({ notifyClient, i18n }) => async ({ - user, - verifyUrl, -}) => { - let templateId = NOTIFICATION_VERIFICATION_EMAIL_EN - if (user.preferredLang === 'french') { - templateId = NOTIFICATION_VERIFICATION_EMAIL_FR - } - - try { - await notifyClient.sendEmail(templateId, user.userName, { - personalisation: { - user: user.displayName, - verify_email_url: verifyUrl, - }, - }) - return true - } catch (err) { - console.error( - `Error occurred when sending verification email for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send verification email. Please try again.`), - ) - } -} diff --git a/api-js/src/on-connect.js b/api-js/src/on-connect.js deleted file mode 100644 index 7df0f99cb8..0000000000 --- a/api-js/src/on-connect.js +++ /dev/null @@ -1,44 +0,0 @@ -export const customOnConnect = - ({ - createContext, - serverContext, - createI18n, - verifyToken, - userRequired, - loadUserByKey, - verifiedRequired, - }) => - async (connectionParams, _webSocket, context) => { - const expandedContext = { ...serverContext, ...context } - - const factoryFunc = createContext(expandedContext) - - const language = connectionParams?.AcceptLanguage - - const authorization = connectionParams?.authorization - - const i18n = createI18n(language) - const verify = verifyToken({ i18n }) - const token = authorization || '' - - let userKey - if (token !== '') { - userKey = verify({ token }).userKey - } - - const { query } = serverContext - - const user = await userRequired({ - i18n, - userKey, - loadUserByKey: loadUserByKey({ query, userKey }), - })() - - verifiedRequired({ user }) - - console.info(`User: ${userKey}, connected to subscription.`) - - const finalContext = await factoryFunc({ req: context.request, connection: { language, authorization } }) - - return finalContext - } diff --git a/api-js/src/organization/index.js b/api-js/src/organization/index.js deleted file mode 100644 index e170187ec3..0000000000 --- a/api-js/src/organization/index.js +++ /dev/null @@ -1,6 +0,0 @@ -export * from './inputs' -export * from './loaders' -export * from './mutations' -export * from './objects' -export * from './queries' -export * from './unions' diff --git a/api-js/src/organization/inputs/__tests__/organization-order.test.js b/api-js/src/organization/inputs/__tests__/organization-order.test.js deleted file mode 100644 index f987ff0ae7..0000000000 --- a/api-js/src/organization/inputs/__tests__/organization-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { organizationOrder } from '../organization-order' -import { OrderDirection, OrganizationOrderField } from '../../../enums' - -describe('given the organizationOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = organizationOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = organizationOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(OrganizationOrderField), - ) - }) - }) -}) diff --git a/api-js/src/organization/loaders/index.js b/api-js/src/organization/loaders/index.js deleted file mode 100644 index 4c2aee2990..0000000000 --- a/api-js/src/organization/loaders/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './load-organization-by-key' -export * from './load-organization-by-slug' -export * from './load-organization-connections-by-domain-id' -export * from './load-organization-connections-by-user-id' diff --git a/api-js/src/organization/loaders/load-organization-by-key.js b/api-js/src/organization/loaders/load-organization-by-key.js deleted file mode 100644 index 8ab995c432..0000000000 --- a/api-js/src/organization/loaders/load-organization-by-key.js +++ /dev/null @@ -1,52 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadOrgByKey = ({ query, language, userKey, i18n }) => - new DataLoader(async (ids) => { - let cursor - - try { - cursor = await query` - WITH claims, domains, organizations - FOR org IN organizations - FILTER org._key IN ${ids} - LET orgDomains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - RETURN MERGE( - { - _id: org._id, - _key: org._key, - _rev: org._rev, - _type: "organization", - id: org._key, - verified: org.verified, - domainCount: COUNT(orgDomains), - summaries: org.summaries - }, - TRANSLATE(${language}, org.orgDetails) - ) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadOrgByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) - } - - const orgMap = {} - try { - await cursor.forEach((org) => { - orgMap[org._key] = org - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} during loadOrgByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) - } - - return ids.map((id) => orgMap[id]) - }) diff --git a/api-js/src/organization/loaders/load-organization-by-slug.js b/api-js/src/organization/loaders/load-organization-by-slug.js deleted file mode 100644 index d6207b068e..0000000000 --- a/api-js/src/organization/loaders/load-organization-by-slug.js +++ /dev/null @@ -1,56 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadOrgBySlug = ({ query, language, userKey, i18n }) => - new DataLoader(async (slugs) => { - let cursor - - try { - cursor = await query` - WITH claims, domains, organizations - FOR org IN organizations - FILTER org.orgDetails.en.slug IN ${slugs} - OR org.orgDetails.fr.slug IN ${slugs} - LET orgDomains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - RETURN MERGE( - { - _id: org._id, - _key: org._key, - _rev: org._rev, - _type: "organization", - id: org._key, - verified: org.verified, - domainCount: COUNT(orgDomains), - summaries: org.summaries, - slugEN: org.orgDetails.en.slug, - slugFR: org.orgDetails.fr.slug - }, - TRANSLATE(${language}, org.orgDetails) - ) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadOrgBySlug: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) - } - - const orgMap = {} - try { - await cursor.forEach((org) => { - orgMap[org.slugEN] = org - orgMap[org.slugFR] = org - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadOrgBySlug: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) - } - - return slugs.map((slug) => orgMap[slug]) - }) diff --git a/api-js/src/organization/mutations/__tests__/create-organization.test.js b/api-js/src/organization/mutations/__tests__/create-organization.test.js deleted file mode 100644 index ee67fdd056..0000000000 --- a/api-js/src/organization/mutations/__tests__/create-organization.test.js +++ /dev/null @@ -1,1029 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { cleanseInput, slugify } from '../../../validators' -import { userRequired, verifiedRequired } from '../../../auth' -import { loadUserByKey } from '../../../user/loaders' -import { loadOrgBySlug } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env - -describe('create an organization', () => { - let query, drop, truncate, schema, collections, transaction, user - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful org creation', () => { - beforeEach(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('language is set to english', () => { - it('returns the organizations information', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadOrgBySlug: loadOrgBySlug({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const orgCursor = await query` - FOR org IN organizations - FILTER (LOWER("treasury-board-of-canada-secretariat") == LOWER(TRANSLATE("en", org.orgDetails).slug)) - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, verified: org.verified }, TRANSLATE("en", org.orgDetails)) - ` - - const org = await orgCursor.next() - - const expectedResponse = { - data: { - createOrganization: { - result: { - id: `${toGlobalId('organization', org._key)}`, - acronym: org.acronym, - slug: org.slug, - name: org.name, - zone: org.zone, - sector: org.sector, - country: org.country, - province: org.province, - city: org.city, - verified: org.verified, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created a new organization: ${org.slug}`, - ]) - }) - }) - describe('language is set to french', () => { - it('returns the organizations information', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - } - } - } - `, - null, - { - request: { - language: 'fr', - }, - query, - collections, - transaction, - userKey: user._key, - auth: { - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - loaders: { - loadOrgBySlug: loadOrgBySlug({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const orgCursor = await query` - FOR org IN organizations - FILTER (LOWER("secretariat-du-conseil-tresor-du-canada") == LOWER(TRANSLATE("fr", org.orgDetails).slug)) - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, verified: org.verified }, TRANSLATE("fr", org.orgDetails)) - ` - - const org = await orgCursor.next() - - const expectedResponse = { - data: { - createOrganization: { - result: { - id: `${toGlobalId('organization', org._key)}`, - acronym: org.acronym, - slug: org.slug, - name: org.name, - zone: org.zone, - sector: org.sector, - country: org.country, - province: org.province, - city: org.city, - verified: org.verified, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created a new organization: treasury-board-of-canada-secretariat`, - ]) - }) - }) - }) - describe('given an unsuccessful org creation', () => { - let i18n - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('organization already exists', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([{}, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = { - data: { - createOrganization: { - result: { - code: 400, - description: - 'Organization name already in use. Please try again with a different name.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create an organization that already exists: treasury-board-of-canada-secretariat`, - ]) - }) - }) - describe('transaction error occurs', () => { - describe('when inserting organization', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([undefined, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to create organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred when user: 123 was creating new organization treasury-board-of-canada-secretariat: Error: trx step error`, - ]) - }) - }) - describe('when inserting edge', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ next: jest.fn() }) - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([undefined, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to create organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred when inserting edge definition for user: 123 to treasury-board-of-canada-secretariat: Error: trx step error`, - ]) - }) - }) - describe('when committing information to db', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ next: jest.fn() }), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([undefined, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to create organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred when committing new organization: treasury-board-of-canada-secretariat for user: 123 to db: Error: trx commit error`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('organization already exists', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction, - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([{}, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = { - data: { - createOrganization: { - result: { - code: 400, - description: - "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create an organization that already exists: treasury-board-of-canada-secretariat`, - ]) - }) - }) - describe('transaction error occurs', () => { - describe('when inserting organization', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([undefined, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer une organisation. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred when user: 123 was creating new organization treasury-board-of-canada-secretariat: Error: trx step error`, - ]) - }) - }) - describe('when inserting edge', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ next: jest.fn() }) - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([undefined, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer une organisation. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred when inserting edge definition for user: 123 to treasury-board-of-canada-secretariat: Error: trx step error`, - ]) - }) - }) - describe('when committing information to db', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - createOrganization( - input: { - acronymEN: "TBS" - acronymFR: "SCT" - nameEN: "Treasury Board of Canada Secretariat" - nameFR: "Secrétariat du Conseil Trésor du Canada" - zoneEN: "FED" - zoneFR: "FED" - sectorEN: "TBS" - sectorFR: "TBS" - countryEN: "Canada" - countryFR: "Canada" - provinceEN: "Ontario" - provinceFR: "Ontario" - cityEN: "Ottawa" - cityFR: "Ottawa" - } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ next: jest.fn() }), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([undefined, undefined]), - }, - loadUserByKey: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de créer une organisation. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred when committing new organization: treasury-board-of-canada-secretariat for user: 123 to db: Error: trx commit error`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/organization/mutations/__tests__/remove-organization.test.js b/api-js/src/organization/mutations/__tests__/remove-organization.test.js deleted file mode 100644 index 454945d7e9..0000000000 --- a/api-js/src/organization/mutations/__tests__/remove-organization.test.js +++ /dev/null @@ -1,5647 +0,0 @@ -import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { cleanseInput } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' -import { loadUserByKey } from '../../../user/loaders' -import { loadOrgByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('removing an organization', () => { - let query, drop, truncate, schema, collections, transaction, user, i18n - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - describe('given a successful removal', () => { - let org, domain - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - }) - const dkim = await collections.dkim.save({ dkim: true }) - await collections.domainsDKIM.save({ - _from: domain._id, - _to: dkim._id, - }) - const dkimResult = await collections.dkimResults.save({ - dkimResult: true, - }) - await collections.dkimToDkimResults.save({ - _from: dkim._id, - _to: dkimResult._id, - }) - const dmarc = await collections.dmarc.save({ dmarc: true }) - await collections.domainsDMARC.save({ - _from: domain._id, - _to: dmarc._id, - }) - const spf = await collections.spf.save({ spf: true }) - await collections.domainsSPF.save({ - _from: domain._id, - _to: spf._id, - }) - const https = await collections.https.save({ https: true }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, - }) - const ssl = await collections.ssl.save({ ssl: true }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, - }) - const dmarcSummary = await collections.dmarcSummaries.save({ - dmarcSummary: true, - }) - await collections.domainsToDmarcSummaries.save({ - _from: domain._id, - _to: dmarcSummary._id, - }) - }) - afterEach(async () => { - await truncate() - await drop() - }) - describe('users permission is super admin', () => { - describe('org is verified', () => { - beforeEach(async () => { - org = await collections.organizations.save({ - verified: true, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - const superAdminOrg = await collections.organizations.save({ - verified: false, - orgDetails: { - en: { - slug: 'super-admin', - acronym: 'SA', - }, - fr: { - slug: 'super-admin', - acronym: 'SA', - }, - }, - }) - await collections.affiliations.save({ - _from: superAdminOrg._id, - _to: user._id, - permission: 'super_admin', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - describe('it owns the dmarc summary data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes the dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - it('removes the ownership edge', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) - }) - describe('it does not own the dmarc summary data', () => { - it('does not remove the dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - }) - describe('org is the only one claiming the domain', () => { - it('removes the dkim result data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - }) - it('removes the scan data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) - }) - it('removes the domain', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const domainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const domainCheck = await domainCursor.next() - expect(domainCheck).toEqual(undefined) - }) - it('removes the affiliations, and org', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - beforeEach(async () => { - const secondOrg = await collections.organizations.save({}) - await collections.claims.save({ - _from: secondOrg._id, - _to: domain._id, - }) - }) - it('does not remove the dkim result', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toBeDefined() - }) - it('does not remove the scan data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toBeDefined() - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toBeDefined() - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toBeDefined() - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toBeDefined() - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const domainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const domainCheck = await domainCursor.next() - expect(domainCheck).toBeDefined() - }) - it('removes the affiliations, and org', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - organization: { - name: 'Treasury Board of Canada Secretariat', - }, - status: - 'Successfully removed organization: treasury-board-secretariat.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed org: ${org._key}.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - organization: { - name: 'Secrétariat du Conseil Trésor du Canada', - }, - status: - "A réussi à supprimer l'organisation : secretariat-conseil-tresor.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed org: ${org._key}.`, - ]) - }) - }) - }) - describe('org is not verified', () => { - beforeEach(async () => { - org = await collections.organizations.save({ - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - const superAdminOrg = await collections.organizations.save({ - verified: false, - orgDetails: { - en: { - slug: 'super-admin', - acronym: 'SA', - }, - fr: { - slug: 'super-admin', - acronym: 'SA', - }, - }, - }) - await collections.affiliations.save({ - _from: superAdminOrg._id, - _to: user._id, - permission: 'super_admin', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - describe('it owns the dmarc summary data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes the dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - it('removes the ownership edge', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) - }) - describe('it does not own the dmarc summary data', () => { - it('does not remove the dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - }) - describe('org is the only one claiming the domain', () => { - it('removes the dkim result data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - }) - it('removes the scan data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) - }) - it('removes the domain', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const domainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const domainCheck = await domainCursor.next() - expect(domainCheck).toEqual(undefined) - }) - it('removes the affiliations, and org', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - beforeEach(async () => { - const secondOrg = await collections.organizations.save({}) - await collections.claims.save({ - _from: secondOrg._id, - _to: domain._id, - }) - }) - it('does not remove the dkim result data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toBeDefined() - }) - it('does not remove the scan data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toBeDefined() - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toBeDefined() - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toBeDefined() - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toBeDefined() - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const domainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const domainCheck = await domainCursor.next() - expect(domainCheck).toBeDefined() - }) - it('removes the affiliations, and org', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - organization: { - name: 'Treasury Board of Canada Secretariat', - }, - status: - 'Successfully removed organization: treasury-board-secretariat.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed org: ${org._key}.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - organization: { - name: 'Secrétariat du Conseil Trésor du Canada', - }, - status: - "A réussi à supprimer l'organisation : secretariat-conseil-tresor.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed org: ${org._key}.`, - ]) - }) - }) - }) - }) - describe('users permission is admin', () => { - beforeEach(async () => { - org = await collections.organizations.save({ - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - }) - describe('org is not verified', () => { - describe('it owns the dmarc summary data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes the dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - it('removes the ownership edge', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) - }) - describe('it does not own the dmarc summary data', () => { - it('does not remove the dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - }) - describe('org is the only one claiming the domain', () => { - it('removes the dkim result data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - }) - it('removes the scan data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) - }) - it('removes the domain', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const domainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const domainCheck = await domainCursor.next() - expect(domainCheck).toEqual(undefined) - }) - it('removes the affiliations, and org', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - beforeEach(async () => { - const secondOrg = await collections.organizations.save({}) - await collections.claims.save({ - _from: secondOrg._id, - _to: domain._id, - }) - }) - it('does not remove the dkim result data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toBeDefined() - }) - it('does not remove the scan data', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toBeDefined() - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toBeDefined() - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toBeDefined() - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toBeDefined() - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const domainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const domainCheck = await domainCursor.next() - expect(domainCheck).toBeDefined() - }) - it('removes the affiliations, and org', async () => { - await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - organization: { - name: 'Treasury Board of Canada Secretariat', - }, - status: - 'Successfully removed organization: treasury-board-secretariat.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed org: ${org._key}.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - organization: { - name: 'Secrétariat du Conseil Trésor du Canada', - }, - status: - "A réussi à supprimer l'organisation : secretariat-conseil-tresor.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed org: ${org._key}.`, - ]) - }) - }) - }) - }) - }) - describe('given an unsuccessful removal', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('the requested org is undefined', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - code: 400, - description: 'Unable to remove unknown organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove org: 123, but there is no org associated with that id.`, - ]) - }) - }) - describe('given an incorrect permission', () => { - describe('users belong to the org', () => { - describe('users role is admin', () => { - describe('user attempts to remove a verified org', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: true, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact super admin for help with removing organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove org: 123, however the user is not a super admin.`, - ]) - }) - }) - }) - describe('users role is user', () => { - describe('they attempt to remove the org', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with removing organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove org: 123, however the user does not have permission to this organization.`, - ]) - }) - }) - }) - }) - }) - describe('given a database error', () => { - describe('when getting the ownership information', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database Error')) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred for user: 123 while attempting to get dmarcSummaryInfo while removing org: 123, Error: Database Error`, - ]) - }) - }) - describe('when getting the domain claim count', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('Database Error')) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred for user: 123 while attempting to gather domain count while removing org: 123, Error: Database Error`, - ]) - }) - }) - }) - describe('given a cursor error', () => { - describe('when getting getting ownership information', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockRejectedValue(new Error('Cursor Error')), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('Database Error')) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred for user: 123 while attempting to get dmarcSummaryInfo while removing org: 123, Error: Cursor Error`, - ]) - }) - }) - describe('when getting getting domain claim count', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockRejectedValue(new Error('Cursor Error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred for user: 123 while attempting to gather domain count while removing org: 123, Error: Cursor Error`, - ]) - }) - }) - }) - describe('given a trx step error', () => { - describe('when removing dmarc summary data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValueOnce(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove dmarc summaries while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing ownership data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValueOnce(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove ownerships while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing dkim results data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when user: 123 attempted to remove dkim results while removing org: 123: Error: Trx Step`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove scan results while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing domain', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove domains while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing affiliations and org', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove affiliations, and the org while removing org: 123, Error: Trx Step`, - ]) - }) - }) - }) - describe('given a trx commit error', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValueOnce([]).mockReturnValue([]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({}), - commit: jest.fn().mockRejectedValue(new Error('Commit Error')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to remove organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred for user: 123 while attempting remove of org: 123, Error: Commit Error`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('the requested org is undefined', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - code: 400, - description: - 'Impossible de supprimer une organisation inconnue.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove org: 123, but there is no org associated with that id.`, - ]) - }) - }) - describe('given an incorrect permission', () => { - describe('users belong to the org', () => { - describe('users role is admin', () => { - describe('user attempts to remove a verified org', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: true, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove org: 123, however the user is not a super admin.`, - ]) - }) - }) - }) - describe('users role is user', () => { - describe('they attempt to remove the org', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const expectedResponse = { - data: { - removeOrganization: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove org: 123, however the user does not have permission to this organization.`, - ]) - }) - }) - }) - }) - }) - describe('given a database error', () => { - describe('when getting the ownership information', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database Error')) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred for user: 123 while attempting to get dmarcSummaryInfo while removing org: 123, Error: Database Error`, - ]) - }) - }) - describe('when getting the domain claim count', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('Database Error')) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred for user: 123 while attempting to gather domain count while removing org: 123, Error: Database Error`, - ]) - }) - }) - }) - describe('given a cursor error', () => { - describe('when getting getting ownership information', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockRejectedValue(new Error('Cursor Error')), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('Database Error')) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred for user: 123 while attempting to get dmarcSummaryInfo while removing org: 123, Error: Cursor Error`, - ]) - }) - }) - describe('when getting getting domain claim count', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockRejectedValue(new Error('Cursor Error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred for user: 123 while attempting to gather domain count while removing org: 123, Error: Cursor Error`, - ]) - }) - }) - }) - describe('given a trx step error', () => { - describe('when removing dmarc summary data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValueOnce(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove dmarc summaries while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing ownership data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValueOnce(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove ownerships while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing dkim results data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when user: 123 attempted to remove dkim results while removing org: 123: Error: Trx Step`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove scan results while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing domain', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove domains while removing org: 123, Error: Trx Step`, - ]) - }) - }) - describe('when removing affiliations and org', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockRejectedValue(new Error('Trx Step')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred for user: 123 while attempting to remove affiliations, and the org while removing org: 123, Error: Trx Step`, - ]) - }) - }) - }) - describe('given a trx commit error', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValueOnce([]).mockReturnValue([]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({}), - commit: jest.fn().mockRejectedValue(new Error('Commit Error')), - }) - - const response = await graphql( - schema, - ` - mutation { - removeOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de supprimer l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred for user: 123 while attempting remove of org: 123, Error: Commit Error`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/organization/mutations/__tests__/update-organization.test.js b/api-js/src/organization/mutations/__tests__/update-organization.test.js deleted file mode 100644 index b50b67ea51..0000000000 --- a/api-js/src/organization/mutations/__tests__/update-organization.test.js +++ /dev/null @@ -1,4248 +0,0 @@ -import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { cleanseInput, slugify } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' -import { loadUserByKey } from '../../../user/loaders' -import { loadOrgByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('updating an organization', () => { - let query, drop, truncate, schema, collections, transaction, user - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - beforeEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful organization update', () => { - let org - beforeEach(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - }) - afterEach(async () => { - await truncate() - await drop() - }) - describe('users permission level is super_admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - describe('users language is english', () => { - describe('updating the acronym', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymEN: "TEST" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TEST', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the name', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - nameEN: "Test" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Test', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the zone', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - zoneEN: "New Zone" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'New Zone', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the sector', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - sectorEN: "New Sector" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'New Sector', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the country', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - countryEN: "A New Country" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'A New Country', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the province', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - provinceEN: "A New Province" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'A New Province', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the city', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'A New City', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating all organizational fields', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymEN: "NEW_ACRONYM" - nameEN: "New Name" - zoneEN: "New Zone" - sectorEN: "New Sector" - countryEN: "New Country" - provinceEN: "New Province" - cityEN: "New City" - acronymFR: "NOUVEL_ACRONYME" - nameFR: "Nouveau nom" - zoneFR: "Nouvelle zone" - sectorFR: "Nouveau secteur" - countryFR: "Nouveau pays" - provinceFR: "Nouvelle province" - cityFR: "Nouvelle ville" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'NEW_ACRONYM', - name: 'New Name', - zone: 'New Zone', - sector: 'New Sector', - country: 'New Country', - province: 'New Province', - city: 'New City', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - }) - describe('users language is french', () => { - describe('updating the acronym', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymFR: "TEST" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TEST', - city: 'Ottawa', - country: 'Canada', - name: 'Secrétariat du Conseil Trésor du Canada', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the name', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - nameFR: "Test" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - city: 'Ottawa', - country: 'Canada', - name: 'Test', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the zone', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - zoneFR: "Secret Zone" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'Secret Zone', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the sector', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - sectorFR: "New Sector" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'New Sector', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the country', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - countryFR: "A New Country" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'A New Country', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the province', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - provinceFR: "A New Province" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'A New Province', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the city', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - cityFR: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - city: 'A New City', - country: 'Canada', - name: 'Secrétariat du Conseil Trésor du Canada', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating all organizational fields', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymEN: "NEW_ACRONYM" - nameEN: "New Name" - zoneEN: "New Zone" - sectorEN: "New Sector" - countryEN: "New Country" - provinceEN: "New Province" - cityEN: "New City" - acronymFR: "NOUVEL_ACRONYME" - nameFR: "Nouveau nom" - zoneFR: "Nouvelle zone" - sectorFR: "Nouveau secteur" - countryFR: "Nouveau pays" - provinceFR: "Nouvelle province" - cityFR: "Nouvelle ville" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'NOUVEL_ACRONYME', - city: 'Nouvelle ville', - country: 'Nouveau pays', - name: 'Nouveau nom', - province: 'Nouvelle province', - sector: 'Nouveau secteur', - zone: 'Nouvelle zone', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - }) - }) - describe('users permission level is admin', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - }) - }) - describe('users language is english', () => { - describe('updating the acronym', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymEN: "TEST" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TEST', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the name', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - nameEN: "Test" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Test', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the zone', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - zoneEN: "New Zone" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'New Zone', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the sector', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - sectorEN: "New Sector" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'New Sector', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the country', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - countryEN: "A New Country" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'A New Country', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the province', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - provinceEN: "A New Province" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'A New Province', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the city', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'A New City', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating all organizational fields', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymEN: "NEW_ACRONYM" - nameEN: "New Name" - zoneEN: "New Zone" - sectorEN: "New Sector" - countryEN: "New Country" - provinceEN: "New Province" - cityEN: "New City" - acronymFR: "NOUVEL_ACRONYME" - nameFR: "Nouveau nom" - zoneFR: "Nouvelle zone" - sectorFR: "Nouveau secteur" - countryFR: "Nouveau pays" - provinceFR: "Nouvelle province" - cityFR: "Nouvelle ville" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'NEW_ACRONYM', - name: 'New Name', - zone: 'New Zone', - sector: 'New Sector', - country: 'New Country', - province: 'New Province', - city: 'New City', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - }) - describe('users language is french', () => { - describe('updating the acronym', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymFR: "TEST" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TEST', - city: 'Ottawa', - country: 'Canada', - name: 'Secrétariat du Conseil Trésor du Canada', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the name', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - nameFR: "Test" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - city: 'Ottawa', - country: 'Canada', - name: 'Test', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the zone', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - zoneFR: "Secret Zone" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'Secret Zone', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the sector', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - sectorFR: "New Sector" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'New Sector', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the country', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - countryFR: "A New Country" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'A New Country', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the province', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - provinceFR: "A New Province" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'A New Province', - city: 'Ottawa', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating the city', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - cityFR: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - city: 'A New City', - country: 'Canada', - name: 'Secrétariat du Conseil Trésor du Canada', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - describe('updating all organizational fields', () => { - it('returns the updated organization', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', org._key)}" - acronymEN: "NEW_ACRONYM" - nameEN: "New Name" - zoneEN: "New Zone" - sectorEN: "New Sector" - countryEN: "New Country" - provinceEN: "New Province" - cityEN: "New City" - acronymFR: "NOUVEL_ACRONYME" - nameFR: "Nouveau nom" - zoneFR: "Nouvelle zone" - sectorFR: "Nouveau secteur" - countryFR: "Nouveau pays" - provinceFR: "Nouvelle province" - cityFR: "Nouvelle ville" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'NOUVEL_ACRONYME', - city: 'Nouvelle ville', - country: 'Nouveau pays', - name: 'Nouveau nom', - province: 'Nouvelle province', - sector: 'Nouveau secteur', - zone: 'Nouvelle zone', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully updated org ${org._key}.`, - ]) - }) - }) - }) - }) - }) - describe('given an unsuccessful organization update', () => { - let i18n - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('organization cannot be found', () => { - describe('organization does not exist in database', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { id: "${toGlobalId( - 'organization', - 1, - )}", cityEN: "A New City" } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 400, - description: 'Unable to update unknown organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization: 1, however no organizations is associated with that id.`, - ]) - }) - }) - }) - describe('user is located in the database', () => { - describe('user does not have the proper permissions', () => { - describe('user has user level permission', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', 123)}" - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest - .fn() - .mockReturnValue({ _id: 'organizations/123' }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with updating organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization 123, however they do not have the correct permission level. Permission: user`, - ]) - }) - }) - describe('user does not belong to that organization', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', 123)}" - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest - .fn() - .mockReturnValue({ _id: 'organizations/123' }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with updating organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization 123, however they do not have the correct permission level. Permission: undefined`, - ]) - }) - }) - }) - }) - describe('organization name is already in use', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - nameEN: "Treasury Board of Canada Secretariat" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 400, - description: - 'Organization name already in use, please choose another and try again.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to change the name of org: 123 however it is already in use.`, - ]) - }) - }) - describe('cursor error occurs', () => { - describe('when gathering comparison org details', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - next() { - throw new Error('Database error occurred.') - }, - }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to update organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while retrieving org: 123 for update, err: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('database error occurs', () => { - describe('when gathering comparison org details', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to update organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while retrieving org: 123 for update, err: Error: Database error occurred.`, - ]) - }) - }) - describe('when checking to see if orgName is already in use', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - nameEN: "Treasury Board of Canada Secretariat" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to update organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred during name check when user: 123 attempted to update org: 123, Error: Database error occurred.`, - ]) - }) - }) - }) - describe('transaction error occurs', () => { - describe('when updating/inserting new org details', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to update organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred while upserting org: 123, err: Error: trx step error`, - ]) - }) - }) - describe('when committing transaction', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to update organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing org: 123, err: Error: trx commit error`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('organization cannot be found', () => { - describe('organization does not exist in database', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { id: "${toGlobalId( - 'organization', - 1, - )}", cityEN: "A New City" } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 400, - description: - 'Impossible de mettre à jour une organisation inconnue.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization: 1, however no organizations is associated with that id.`, - ]) - }) - }) - }) - describe('user is located in the database', () => { - describe('user does not have the proper permissions', () => { - describe('user has user level permission', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', 123)}" - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest - .fn() - .mockReturnValue({ _id: 'organizations/123' }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization 123, however they do not have the correct permission level. Permission: user`, - ]) - }) - }) - describe('user does not belong to that organization', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization ( - input: { - id: "${toGlobalId('organization', 123)}" - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest - .fn() - .mockReturnValue({ _id: 'organizations/123' }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization 123, however they do not have the correct permission level. Permission: undefined`, - ]) - }) - }) - }) - }) - describe('organization name is already in use', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - nameEN: "Treasury Board of Canada Secretariat" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = { - data: { - updateOrganization: { - result: { - code: 400, - description: - "Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to change the name of org: 123 however it is already in use.`, - ]) - }) - }) - describe('cursor error occurs', () => { - describe('when gathering comparison org details', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - next() { - throw new Error('Database error occurred.') - }, - }), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while retrieving org: 123 for update, err: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('database error occurs', () => { - describe('when gathering comparison org details', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while retrieving org: 123 for update, err: Error: Database error occurred.`, - ]) - }) - }) - describe('when checking to see if orgName is already in use', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - nameEN: "Treasury Board of Canada Secretariat" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred during name check when user: 123 attempted to update org: 123, Error: Database error occurred.`, - ]) - }) - }) - }) - describe('transaction error occurs', () => { - describe('when updating/inserting new org details', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred while upserting org: 123, err: Error: trx step error`, - ]) - }) - }) - describe('when committing transaction', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateOrganization( - input: { - id: "${toGlobalId('organization', 123)}", - cityEN: "A New City" - } - ) { - result { - ... on Organization { - acronym - name - zone - sector - country - province - city - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }), - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ _key: 123 }), - verifiedRequired: jest.fn(), - }, - validators: { - cleanseInput, - slugify, - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - name: 'Treasury Board of Canada Secretariat', - _key: 123, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing org: 123, err: Error: trx commit error`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/organization/mutations/__tests__/verify-organization.test.js b/api-js/src/organization/mutations/__tests__/verify-organization.test.js deleted file mode 100644 index 4de445a4ec..0000000000 --- a/api-js/src/organization/mutations/__tests__/verify-organization.test.js +++ /dev/null @@ -1,1294 +0,0 @@ -import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { cleanseInput } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' -import { loadUserByKey } from '../../../user/loaders' -import { loadOrgByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('removing an organization', () => { - let query, drop, truncate, schema, collections, transaction, i18n, user - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(async () => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful org verification', () => { - let org - beforeAll(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - verified: false, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'super_admin', - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('super admin is able to verify organization', () => { - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - i18n, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - i18n, - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - userKey: user._key, - i18n, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - }, - ) - - const expectedResponse = { - data: { - verifyOrganization: { - result: { - status: - 'Successfully verified organization: treasury-board-secretariat.', - organization: { - name: 'Treasury Board of Canada Secretariat', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully verified org: ${org._key}.`, - ]) - - const orgLoader = loadOrgByKey({ - query, - language: 'en', - userKey: user._key, - i18n, - }) - const verifiedOrg = await orgLoader.load(org._key) - expect(verifiedOrg.verified).toEqual(true) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('super admin is able to verify organization', () => { - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', org._key)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - i18n, - }), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - i18n, - }), - verifiedRequired: verifiedRequired({}), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'fr', - userKey: user._key, - i18n, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - }, - ) - - const expectedResponse = { - data: { - verifyOrganization: { - result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", - organization: { - name: 'Secrétariat du Conseil Trésor du Canada', - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully verified org: ${org._key}.`, - ]) - - const orgLoader = loadOrgByKey({ - query, - language: 'fr', - userKey: user._key, - i18n, - }) - const verifiedOrg = await orgLoader.load(org._key) - expect(verifiedOrg.verified).toEqual(true) - }) - }) - }) - }) - describe('given an unsuccessful org verification', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('organization is not found', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', -1)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 400, - description: 'Unable to verify unknown organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: -1, however no organizations is associated with that id.`, - ]) - }) - }) - describe('user permission is not super admin', () => { - describe('users permission level is admin', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _id: 'organizations/123', - }), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact super admin for help with verifying this organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: admin`, - ]) - }) - }) - describe('users permission level is user', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _id: 'organizations/123', - }), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact super admin for help with verifying this organization.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: user`, - ]) - }) - }) - }) - describe('organization is already verified', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: true, - }), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 400, - description: 'Organization has already been verified.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: 123, however the organization has already been verified.`, - ]) - }) - }) - describe('transaction error occurs', () => { - describe('when stepping transaction', () => { - describe('when upserting org information', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to verify organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - `Transaction error occurred while upserting verified org: 123, err: Error: trx step error`, - ]) - }) - }) - describe('when clearing owners', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to verify organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - `Trx step error occurred when clearing owners for org: 123: Error: trx step error`, - ]) - }) - }) - }) - describe('when committing transaction', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Unable to verify organization. Please try again.', - ), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing newly verified org: 123, err: Error: trx commit error`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('organization is not found', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', -1)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 400, - description: - 'Impossible de vérifier une organisation inconnue.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: -1, however no organizations is associated with that id.`, - ]) - }) - }) - describe('user permission is not super admin', () => { - describe('users permission level is admin', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _id: 'organizations/123', - }), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: admin`, - ]) - }) - }) - describe('users permission level is user', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('user'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _id: 'organizations/123', - }), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 403, - description: - "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: user`, - ]) - }) - }) - }) - describe('organization is already verified', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: true, - }), - }, - }, - }, - ) - - const error = { - data: { - verifyOrganization: { - result: { - code: 400, - description: "L'organisation a déjà été vérifiée.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to verify organization: 123, however the organization has already been verified.`, - ]) - }) - }) - describe('transaction error occurs', () => { - describe('when stepping transaction', () => { - describe('when upserting org information', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de vérifier l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - `Transaction error occurred while upserting verified org: 123, err: Error: trx step error`, - ]) - }) - }) - describe('when clearing owners', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de vérifier l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - `Trx step error occurred when clearing owners for org: 123: Error: trx step error`, - ]) - }) - }) - }) - describe('when committing transaction', () => { - it('throws an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - verifyOrganization( - input: { - orgId: "${toGlobalId('organization', 123)}" - } - ) { - result { - ... on OrganizationResult { - status - organization { - name - } - } - ... on OrganizationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de vérifier l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing newly verified org: 123, err: Error: trx commit error`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/organization/mutations/create-organization.js b/api-js/src/organization/mutations/create-organization.js deleted file mode 100644 index c00e5bf41c..0000000000 --- a/api-js/src/organization/mutations/create-organization.js +++ /dev/null @@ -1,250 +0,0 @@ -import { GraphQLNonNull, GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { Acronym } from '../../scalars' -import { createOrganizationUnion } from '../unions' - -export const createOrganization = new mutationWithClientMutationId({ - name: 'CreateOrganization', - description: - 'This mutation allows the creation of an organization inside the database.', - inputFields: () => ({ - acronymEN: { - type: GraphQLNonNull(Acronym), - description: 'The English acronym of the organization.', - }, - acronymFR: { - type: GraphQLNonNull(Acronym), - description: 'The French acronym of the organization.', - }, - nameEN: { - type: GraphQLNonNull(GraphQLString), - description: 'The English name of the organization.', - }, - nameFR: { - type: GraphQLNonNull(GraphQLString), - description: 'The French name of the organization.', - }, - zoneEN: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the zone the organization belongs to.', - }, - zoneFR: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the zone the organization belongs to.', - }, - sectorEN: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the sector the organization belongs to.', - }, - sectorFR: { - type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the sector the organization belongs to.', - }, - countryEN: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the country the organization resides in.', - }, - countryFR: { - type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the country the organization resides in.', - }, - provinceEN: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the province the organization resides in.', - }, - provinceFR: { - type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the province the organization resides in.', - }, - cityEN: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the city the organization resides in.', - }, - cityFR: { - type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the city the organization resides in.', - }, - }), - outputFields: () => ({ - result: { - type: createOrganizationUnion, - description: - '`CreateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - request, - collections, - transaction, - query, - userKey, - auth: { userRequired, verifiedRequired }, - loaders: { loadOrgBySlug }, - validators: { cleanseInput, slugify }, - }, - ) => { - // Get user - const user = await userRequired() - - verifiedRequired({ user }) - - // Cleanse Input - const acronymEN = cleanseInput(args.acronymEN) - const acronymFR = cleanseInput(args.acronymFR) - const nameEN = cleanseInput(args.nameEN) - const nameFR = cleanseInput(args.nameFR) - const zoneEN = cleanseInput(args.zoneEN) - const zoneFR = cleanseInput(args.zoneFR) - const sectorEN = cleanseInput(args.sectorEN) - const sectorFR = cleanseInput(args.sectorFR) - const countryEN = cleanseInput(args.countryEN) - const countryFR = cleanseInput(args.countryFR) - const provinceEN = cleanseInput(args.provinceEN) - const provinceFR = cleanseInput(args.provinceFR) - const cityEN = cleanseInput(args.cityEN) - const cityFR = cleanseInput(args.cityFR) - - // Create EN and FR slugs - const slugEN = slugify(nameEN) - const slugFR = slugify(nameFR) - - // Check to see if org already exists - const [orgEN, orgFR] = await loadOrgBySlug.loadMany([slugEN, slugFR]) - - if (typeof orgEN !== 'undefined' || typeof orgFR !== 'undefined') { - console.warn( - `User: ${userKey} attempted to create an organization that already exists: ${slugEN}`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Organization name already in use. Please try again with a different name.`, - ), - } - } - - // Create new organization - const organizationDetails = { - verified: false, - orgDetails: { - en: { - slug: slugEN, - acronym: acronymEN, - name: nameEN, - zone: zoneEN, - sector: sectorEN, - country: countryEN, - province: provinceEN, - city: cityEN, - }, - fr: { - slug: slugFR, - acronym: acronymFR, - name: nameFR, - zone: zoneFR, - sector: sectorFR, - country: countryFR, - province: provinceFR, - city: cityFR, - }, - }, - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - let cursor - try { - cursor = await trx.step( - () => - query` - WITH organizations - INSERT ${organizationDetails} INTO organizations - RETURN MERGE( - { - _id: NEW._id, - _key: NEW._key, - _rev: NEW._rev, - _type: "organization", - id: NEW._key, - verified: NEW.verified, - domainCount: 0, - summaries: NEW.summaries - }, - TRANSLATE(${request.language}, NEW.orgDetails) - ) - `, - ) - } catch (err) { - console.error( - `Transaction error occurred when user: ${userKey} was creating new organization ${slugEN}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to create organization. Please try again.`), - ) - } - const organization = await cursor.next() - - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - INSERT { - _from: ${organization._id}, - _to: ${user._id}, - permission: "admin", - owner: true - } INTO affiliations - `, - ) - } catch (err) { - console.error( - `Transaction error occurred when inserting edge definition for user: ${userKey} to ${slugEN}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to create organization. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction error occurred when committing new organization: ${slugEN} for user: ${userKey} to db: ${err}`, - ) - throw new Error( - i18n._(t`Unable to create organization. Please try again.`), - ) - } - - console.info( - `User: ${userKey} successfully created a new organization: ${slugEN}`, - ) - - return organization - }, -}) diff --git a/api-js/src/organization/mutations/index.js b/api-js/src/organization/mutations/index.js deleted file mode 100644 index a2e9b7d45f..0000000000 --- a/api-js/src/organization/mutations/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './create-organization' -export * from './remove-organization' -export * from './update-organization' -export * from './verify-organization' diff --git a/api-js/src/organization/mutations/remove-organization.js b/api-js/src/organization/mutations/remove-organization.js deleted file mode 100644 index 541c5a7acc..0000000000 --- a/api-js/src/organization/mutations/remove-organization.js +++ /dev/null @@ -1,476 +0,0 @@ -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { removeOrganizationUnion } from '../unions' - -export const removeOrganization = new mutationWithClientMutationId({ - name: 'RemoveOrganization', - description: 'This mutation allows the removal of unused organizations.', - inputFields: () => ({ - orgId: { - type: GraphQLNonNull(GraphQLID), - description: 'The global id of the organization you wish you remove.', - }, - }), - outputFields: () => ({ - result: { - type: GraphQLNonNull(removeOrganizationUnion), - description: - '`RemoveOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - validators: { cleanseInput }, - loaders: { loadOrgByKey }, - }, - ) => { - // Get user - const user = await userRequired() - - verifiedRequired({ user }) - - // Cleanse Input - const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - - // Get org from db - const organization = await loadOrgByKey.load(orgId) - - // Check to see if org exists - if (!organization) { - console.warn( - `User: ${userKey} attempted to remove org: ${orgId}, but there is no org associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to remove unknown organization.`), - } - } - - // Get users permission - const permission = await checkPermission({ orgId: organization._id }) - - if (permission !== 'super_admin' && permission !== 'admin') { - console.warn( - `User: ${userKey} attempted to remove org: ${organization._key}, however the user does not have permission to this organization.`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with removing organization.`, - ), - } - } - - // Check to see if org is verified check, and the user is super admin - if (organization.verified && permission !== 'super_admin') { - console.warn( - `User: ${userKey} attempted to remove org: ${organization._key}, however the user is not a super admin.`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact super admin for help with removing organization.`, - ), - } - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - // check to see if org has any dmarc summaries - let dmarcSummaryCheckCursor - try { - dmarcSummaryCheckCursor = await query` - WITH domains, ownership, dmarcSummaries, organizations - FOR v, e IN 1..1 OUTBOUND ${organization._id} ownership - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred for user: ${userKey} while attempting to get dmarcSummaryInfo while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - let dmarcSummaryCheckList - try { - dmarcSummaryCheckList = await dmarcSummaryCheckCursor.all() - } catch (err) { - console.error( - `Cursor error occurred for user: ${userKey} while attempting to get dmarcSummaryInfo while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - for (const ownership of dmarcSummaryCheckList) { - try { - await trx.step( - () => query` - WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries - LET dmarcSummaryEdges = ( - FOR v, e IN 1..1 OUTBOUND ${ownership._to} domainsToDmarcSummaries - RETURN { edgeKey: e._key, dmarcSummaryId: e._to } - ) - LET removeDmarcSummaryEdges = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries - OPTIONS { waitForSync: true } - ) - LET removeDmarcSummary = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key - REMOVE key IN dmarcSummaries - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred for user: ${userKey} while attempting to remove dmarc summaries while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - try { - await trx.step( - () => query` - WITH ownership, organizations, domains - REMOVE ${ownership._key} IN ownership - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred for user: ${userKey} while attempting to remove ownerships while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - } - - // check to see if any other orgs are using this domain - let countCursor - try { - countCursor = await query` - WITH claims, domains, organizations - LET domainIds = ( - FOR v, e IN 1..1 OUTBOUND ${organization._id} claims - RETURN e._to - ) - FOR domain IN domains - FILTER domain._id IN domainIds - LET count = LENGTH( - FOR v, e IN 1..1 INBOUND domain._id claims - RETURN 1 - ) - RETURN { - _id: domain._id, - _key: domain._key, - domain: domain.domain, - count - } - ` - } catch (err) { - console.error( - `Database error occurred for user: ${userKey} while attempting to gather domain count while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - let domainInfo - try { - domainInfo = await countCursor.all() - } catch (err) { - console.error( - `Cursor error occurred for user: ${userKey} while attempting to gather domain count while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - for (const domain of domainInfo) { - if (domain.count === 1) { - try { - await trx.step( - () => - query` - WITH claims, dkim, domains, domainsDKIM, organizations, dkimToDkimResults, dkimResults - LET dkimEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsDKIM - RETURN { edgeKey: e._key, dkimId: e._to } - ) - FOR dkimEdge IN dkimEdges - LET dkimResultEdges = ( - FOR v, e IN 1..1 OUTBOUND dkimEdge.dkimId dkimToDkimResults - RETURN { edgeKey: e._key, dkimResultId: e._to } - ) - LET removeDkimResultEdges = ( - FOR dkimResultEdge IN dkimResultEdges - REMOVE dkimResultEdge.edgeKey IN dkimToDkimResults - OPTIONS { waitForSync: true } - ) - LET removeDkimResult = ( - FOR dkimResultEdge IN dkimResultEdges - LET key = PARSE_IDENTIFIER(dkimResultEdge.dkimResultId).key - REMOVE key IN dkimResults - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when user: ${userKey} attempted to remove dkim results while removing org: ${organization._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - try { - await Promise.all([ - trx.step( - () => - query` - WITH claims, dkim, domains, domainsDKIM, organizations - LET dkimEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsDKIM - RETURN { edgeKey: e._key, dkimId: e._to } - ) - LET removeDkimEdges = ( - FOR dkimEdge IN dkimEdges - REMOVE dkimEdge.edgeKey IN domainsDKIM - OPTIONS { waitForSync: true } - ) - LET removeDkim = ( - FOR dkimEdge IN dkimEdges - LET key = PARSE_IDENTIFIER(dkimEdge.dkimId).key - REMOVE key IN dkim - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, dmarc, domains, domainsDMARC, organizations - LET dmarcEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsDMARC - RETURN { edgeKey: e._key, dmarcId: e._to } - ) - LET removeDmarcEdges = ( - FOR dmarcEdge IN dmarcEdges - REMOVE dmarcEdge.edgeKey IN domainsDMARC - OPTIONS { waitForSync: true } - ) - LET removeDmarc = ( - FOR dmarcEdge IN dmarcEdges - LET key = PARSE_IDENTIFIER(dmarcEdge.dmarcId).key - REMOVE key IN dmarc - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, domains, domainsSPF, organizations, spf - LET spfEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsSPF - RETURN { edgeKey: e._key, spfId: e._to } - ) - LET removeSpfEdges = ( - FOR spfEdge IN spfEdges - REMOVE spfEdge.edgeKey IN domainsSPF - OPTIONS { waitForSync: true } - ) - LET removeSpf = ( - FOR spfEdge IN spfEdges - LET key = PARSE_IDENTIFIER(spfEdge.spfId).key - REMOVE key IN spf - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, domains, domainsHTTPS, https, organizations - LET httpsEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsHTTPS - RETURN { edgeKey: e._key, httpsId: e._to } - ) - LET removeHttpsEdges = ( - FOR httpsEdge IN httpsEdges - REMOVE httpsEdge.edgeKey IN domainsHTTPS - OPTIONS { waitForSync: true } - ) - LET removeHttps = ( - FOR httpsEdge IN httpsEdges - LET key = PARSE_IDENTIFIER(httpsEdge.httpsId).key - REMOVE key IN https - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH claims, domains, domainsSSL, organizations, ssl - LET sslEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsSSL - RETURN { edgeKey: e._key, sslId: e._to} - ) - LET removeSslEdges = ( - FOR sslEdge IN sslEdges - REMOVE sslEdge.edgeKey IN domainsSSL - OPTIONS { waitForSync: true } - ) - LET removeSsl = ( - FOR sslEdge IN sslEdges - LET key = PARSE_IDENTIFIER(sslEdge.sslId).key - REMOVE key IN ssl - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - ]) - } catch (err) { - console.error( - `Trx step error occurred for user: ${userKey} while attempting to remove scan results while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - try { - await trx.step( - () => - query` - WITH claims, domains, organizations - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${organization._id} claims - FILTER e._to == ${domain._id} - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge in domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - LET removeDomain = ( - FOR domainEdge in domainEdges - LET key = PARSE_IDENTIFIER(domainEdge.domainId).key - REMOVE key IN domains - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred for user: ${userKey} while attempting to remove domains while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - } - } - - try { - await Promise.all([ - trx.step( - () => - query` - WITH affiliations, organizations, users - LET userEdges = ( - FOR v, e IN 1..1 OUTBOUND ${organization._id} affiliations - RETURN { edgeKey: e._key, userKey: e._to } - ) - LET removeUserEdges = ( - FOR userEdge IN userEdges - REMOVE userEdge.edgeKey IN affiliations - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => - query` - WITH organizations - REMOVE ${organization._key} IN organizations - OPTIONS { waitForSync: true } - `, - ), - ]) - } catch (err) { - console.error( - `Trx step error occurred for user: ${userKey} while attempting to remove affiliations, and the org while removing org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred for user: ${userKey} while attempting remove of org: ${organization._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) - } - - console.info( - `User: ${userKey} successfully removed org: ${organization._key}.`, - ) - - return { - _type: 'result', - status: i18n._( - t`Successfully removed organization: ${organization.slug}.`, - ), - organization, - } - }, -}) diff --git a/api-js/src/organization/mutations/update-organization.js b/api-js/src/organization/mutations/update-organization.js deleted file mode 100644 index a947f86ce5..0000000000 --- a/api-js/src/organization/mutations/update-organization.js +++ /dev/null @@ -1,296 +0,0 @@ -import { GraphQLString, GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { Acronym } from '../../scalars' -import { updateOrganizationUnion } from '../unions' - -export const updateOrganization = new mutationWithClientMutationId({ - name: 'UpdateOrganization', - description: - 'Mutation allows the modification of organizations if any changes to the organization may occur.', - inputFields: () => ({ - id: { - type: GraphQLNonNull(GraphQLID), - description: 'The global id of the organization to be updated.', - }, - acronymEN: { - type: Acronym, - description: 'The English acronym of the organization.', - }, - acronymFR: { - type: Acronym, - description: 'The French acronym of the organization.', - }, - nameEN: { - type: GraphQLString, - description: 'The English name of the organization.', - }, - nameFR: { - type: GraphQLString, - description: 'The French name of the organization.', - }, - zoneEN: { - type: GraphQLString, - description: - 'The English translation of the zone the organization belongs to.', - }, - zoneFR: { - type: GraphQLString, - description: - 'The English translation of the zone the organization belongs to.', - }, - sectorEN: { - type: GraphQLString, - description: - 'The English translation of the sector the organization belongs to.', - }, - sectorFR: { - type: GraphQLString, - description: - 'The French translation of the sector the organization belongs to.', - }, - countryEN: { - type: GraphQLString, - description: - 'The English translation of the country the organization resides in.', - }, - countryFR: { - type: GraphQLString, - description: - 'The French translation of the country the organization resides in.', - }, - provinceEN: { - type: GraphQLString, - description: - 'The English translation of the province the organization resides in.', - }, - provinceFR: { - type: GraphQLString, - description: - 'The French translation of the province the organization resides in.', - }, - cityEN: { - type: GraphQLString, - description: - 'The English translation of the city the organization resides in.', - }, - cityFR: { - type: GraphQLString, - description: - 'The French translation of the city the organization resides in.', - }, - }), - outputFields: () => ({ - result: { - type: GraphQLNonNull(updateOrganizationUnion), - description: - '`UpdateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgByKey }, - validators: { cleanseInput, slugify }, - }, - ) => { - // Get user - const user = await userRequired() - - verifiedRequired({ user }) - - // Cleanse Input - const { type: _orgType, id: orgKey } = fromGlobalId(cleanseInput(args.id)) - const acronymEN = cleanseInput(args.acronymEN) - const acronymFR = cleanseInput(args.acronymFR) - const nameEN = cleanseInput(args.nameEN) - const nameFR = cleanseInput(args.nameFR) - const zoneEN = cleanseInput(args.zoneEN) - const zoneFR = cleanseInput(args.zoneFR) - const sectorEN = cleanseInput(args.sectorEN) - const sectorFR = cleanseInput(args.sectorFR) - const countryEN = cleanseInput(args.countryEN) - const countryFR = cleanseInput(args.countryFR) - const provinceEN = cleanseInput(args.provinceEN) - const provinceFR = cleanseInput(args.provinceFR) - const cityEN = cleanseInput(args.cityEN) - const cityFR = cleanseInput(args.cityFR) - - // Create Slug - const slugEN = slugify(nameEN) - const slugFR = slugify(nameFR) - - // Check to see if org exists - const currentOrg = await loadOrgByKey.load(orgKey) - - if (typeof currentOrg === 'undefined') { - console.warn( - `User: ${userKey} attempted to update organization: ${orgKey}, however no organizations is associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to update unknown organization.`), - } - } - - // Check to see if user has permission - const permission = await checkPermission({ orgId: currentOrg._id }) - - if (permission !== 'admin' && permission !== 'super_admin') { - console.error( - `User: ${userKey} attempted to update organization ${orgKey}, however they do not have the correct permission level. Permission: ${permission}`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating organization.`, - ), - } - } - - // Check to see if any orgs already have the name in use - if (nameEN !== '' || nameFR !== '') { - let orgNameCheckCursor - try { - orgNameCheckCursor = await query` - WITH organizations - FOR org IN organizations - FILTER (org.orgDetails.en.name == ${nameEN}) OR (org.orgDetails.fr.name == ${nameFR}) - RETURN org - ` - } catch (err) { - console.error( - `Database error occurred during name check when user: ${userKey} attempted to update org: ${currentOrg._key}, ${err}`, - ) - throw new Error( - i18n._(t`Unable to update organization. Please try again.`), - ) - } - - if (orgNameCheckCursor.count > 0) { - console.error( - `User: ${userKey} attempted to change the name of org: ${currentOrg._key} however it is already in use.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Organization name already in use, please choose another and try again.`, - ), - } - } - } - - // Get all org details for comparison - let orgCursor - try { - orgCursor = await query` - WITH organizations - FOR org IN organizations - FILTER org._key == ${orgKey} - RETURN org - ` - } catch (err) { - console.error( - `Database error occurred while retrieving org: ${orgKey} for update, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update organization. Please try again.`), - ) - } - - let compareOrg - try { - compareOrg = await orgCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while retrieving org: ${orgKey} for update, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update organization. Please try again.`), - ) - } - - const updatedOrgDetails = { - orgDetails: { - en: { - slug: slugEN || compareOrg.orgDetails.en.slug, - acronym: acronymEN || compareOrg.orgDetails.en.acronym, - name: nameEN || compareOrg.orgDetails.en.name, - zone: zoneEN || compareOrg.orgDetails.en.zone, - sector: sectorEN || compareOrg.orgDetails.en.sector, - country: countryEN || compareOrg.orgDetails.en.country, - province: provinceEN || compareOrg.orgDetails.en.province, - city: cityEN || compareOrg.orgDetails.en.city, - }, - fr: { - slug: slugFR || compareOrg.orgDetails.fr.slug, - acronym: acronymFR || compareOrg.orgDetails.fr.acronym, - name: nameFR || compareOrg.orgDetails.fr.name, - zone: zoneFR || compareOrg.orgDetails.fr.zone, - sector: sectorFR || compareOrg.orgDetails.fr.sector, - country: countryFR || compareOrg.orgDetails.fr.country, - province: provinceFR || compareOrg.orgDetails.fr.province, - city: cityFR || compareOrg.orgDetails.fr.city, - }, - }, - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - // Upsert new org details - try { - await trx.step( - async () => - await query` - WITH organizations - UPSERT { _key: ${orgKey} } - INSERT ${updatedOrgDetails} - UPDATE ${updatedOrgDetails} - IN organizations - `, - ) - } catch (err) { - console.error( - `Transaction error occurred while upserting org: ${orgKey}, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update organization. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction error occurred while committing org: ${orgKey}, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to update organization. Please try again.`), - ) - } - - await loadOrgByKey.clear(orgKey) - const organization = await loadOrgByKey.load(orgKey) - - console.info(`User: ${userKey}, successfully updated org ${orgKey}.`) - return organization - }, -}) diff --git a/api-js/src/organization/mutations/verify-organization.js b/api-js/src/organization/mutations/verify-organization.js deleted file mode 100644 index 0cc835db26..0000000000 --- a/api-js/src/organization/mutations/verify-organization.js +++ /dev/null @@ -1,162 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLNonNull, GraphQLID } from 'graphql' -import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' - -import { verifyOrganizationUnion } from '../unions' - -export const verifyOrganization = new mutationWithClientMutationId({ - name: 'VerifyOrganization', - description: 'Mutation allows the verification of an organization.', - inputFields: () => ({ - orgId: { - type: GraphQLNonNull(GraphQLID), - description: 'The global id of the organization to be verified.', - }, - }), - outputFields: () => ({ - result: { - type: verifyOrganizationUnion, - description: - '`VerifyOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgByKey }, - validators: { cleanseInput }, - }, - ) => { - // Ensure that user is required - const user = await userRequired() - - verifiedRequired({ user }) - - const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) - - // Check to see if org exists - const currentOrg = await loadOrgByKey.load(orgKey) - - if (typeof currentOrg === 'undefined') { - console.warn( - `User: ${userKey} attempted to verify organization: ${orgKey}, however no organizations is associated with that id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to verify unknown organization.`), - } - } - - // Check to see if use has permission - const permission = await checkPermission({ orgId: currentOrg._id }) - - if (permission !== 'super_admin') { - console.warn( - `User: ${userKey} attempted to verify organization: ${orgKey}, however they do not have the correct permission level. Permission: ${permission}`, - ) - return { - _type: 'error', - code: 403, - description: i18n._( - t`Permission Denied: Please contact super admin for help with verifying this organization.`, - ), - } - } - - // Check to see if org is already verified - if (currentOrg.verified) { - console.warn( - `User: ${userKey} attempted to verify organization: ${orgKey}, however the organization has already been verified.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Organization has already been verified.`), - } - } - - // Set org to verified - currentOrg.verified = true - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - // Upsert new org details - try { - await trx.step( - () => - query` - WITH organizations - UPSERT { _key: ${orgKey} } - INSERT ${currentOrg} - UPDATE ${currentOrg} - IN organizations - `, - ) - } catch (err) { - console.error( - `Transaction error occurred while upserting verified org: ${orgKey}, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify organization. Please try again.`), - ) - } - - // Set all affiliation owner fields to false - try { - await trx.step( - () => query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 OUTBOUND ${currentOrg._id} affiliations - UPSERT { _key: e._key } - INSERT { owner: false } - UPDATE { owner: false } - IN affiliations - RETURN e - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when clearing owners for org: ${orgKey}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify organization. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction error occurred while committing newly verified org: ${orgKey}, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify organization. Please try again.`), - ) - } - - console.info(`User: ${userKey}, successfully verified org: ${orgKey}.`) - - return { - _type: 'result', - status: i18n._( - t`Successfully verified organization: ${currentOrg.slug}.`, - ), - organization: currentOrg, - } - }, -}) diff --git a/api-js/src/organization/objects/__tests__/organization-summary.test.js b/api-js/src/organization/objects/__tests__/organization-summary.test.js deleted file mode 100644 index f71b7f3e33..0000000000 --- a/api-js/src/organization/objects/__tests__/organization-summary.test.js +++ /dev/null @@ -1,163 +0,0 @@ -import { organizationSummaryType } from '../organization-summary' -import { categorizedSummaryType } from '../../../summaries/objects' - -describe('given the organization summary object', () => { - describe('testing field definitions', () => { - it('has a mail field', () => { - const demoType = organizationSummaryType.getFields() - - expect(demoType).toHaveProperty('mail') - expect(demoType.mail.type).toMatchObject(categorizedSummaryType) - }) - it('has a web field', () => { - const demoType = organizationSummaryType.getFields() - - expect(demoType).toHaveProperty('web') - expect(demoType.web.type).toMatchObject(categorizedSummaryType) - }) - }) - - describe('field resolvers', () => { - describe('mail resolver', () => { - describe('total is zero', () => { - it('returns the resolved value', () => { - const demoType = organizationSummaryType.getFields() - - const mail = { - pass: 0, - fail: 0, - total: 0, - } - - const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), - } - - expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ - categories: [ - { - count: 0, - name: 'pass', - percentage: 0, - }, - { - count: 0, - name: 'fail', - percentage: 0, - }, - ], - total: 0, - }) - }) - }) - - describe('when total is greater then zero', () => { - it('returns the resolved value', () => { - const demoType = organizationSummaryType.getFields() - - const mail = { - pass: 50, - fail: 1000, - total: 1050, - } - - const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), - } - - expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ - categories: [ - { - count: 50, - name: 'pass', - percentage: 4.8, - }, - { - count: 1000, - name: 'fail', - percentage: 95.2, - }, - ], - total: 1050, - }) - }) - }) - }) - describe('testing web resolver', () => { - describe('total is zero', () => { - it('returns the resolved value', () => { - const demoType = organizationSummaryType.getFields() - - const web = { - pass: 0, - fail: 0, - total: 0, - } - - const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), - } - - expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ - categories: [ - { - count: 0, - name: 'pass', - percentage: 0, - }, - { - count: 0, - name: 'fail', - percentage: 0, - }, - ], - total: 0, - }) - }) - }) - describe('total is greater then zero', () => { - it('returns the resolved value', () => { - const demoType = organizationSummaryType.getFields() - - const web = { - pass: 50, - fail: 1000, - total: 1050, - } - - const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), - } - - expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ - categories: [ - { - count: 50, - name: 'pass', - percentage: 4.8, - }, - { - count: 1000, - name: 'fail', - percentage: 95.2, - }, - ], - total: 1050, - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/organization/objects/organization-summary.js b/api-js/src/organization/objects/organization-summary.js deleted file mode 100644 index 5580e3832b..0000000000 --- a/api-js/src/organization/objects/organization-summary.js +++ /dev/null @@ -1,76 +0,0 @@ -import { GraphQLObjectType } from 'graphql' - -import { categorizedSummaryType } from '../../summaries' - -export const organizationSummaryType = new GraphQLObjectType({ - name: 'OrganizationSummary', - description: 'Summaries based on domains that the organization has claimed.', - fields: () => ({ - mail: { - type: categorizedSummaryType, - description: - 'Summary based on mail scan results for a given organization.', - resolve: ({ mail }, _) => { - let percentPass, percentageFail - if (mail.total <= 0) { - percentPass = 0 - percentageFail = 0 - } else { - percentPass = Number(((mail.pass / mail.total) * 100).toFixed(1)) - percentageFail = Number(((mail.fail / mail.total) * 100).toFixed(1)) - } - - const categories = [ - { - name: 'pass', - count: mail.pass, - percentage: percentPass, - }, - { - name: 'fail', - count: mail.fail, - percentage: percentageFail, - }, - ] - - return { - categories, - total: mail.total, - } - }, - }, - web: { - type: categorizedSummaryType, - description: - 'Summary based on web scan results for a given organization.', - resolve: ({ web }, _) => { - let percentPass, percentageFail - if (web.total <= 0) { - percentPass = 0 - percentageFail = 0 - } else { - percentPass = Number(((web.pass / web.total) * 100).toFixed(1)) - percentageFail = Number(((web.fail / web.total) * 100).toFixed(1)) - } - - const categories = [ - { - name: 'pass', - count: web.pass, - percentage: percentPass, - }, - { - name: 'fail', - count: web.fail, - percentage: percentageFail, - }, - ] - - return { - categories, - total: web.total, - } - }, - }, - }), -}) diff --git a/api-js/src/organization/objects/organization.js b/api-js/src/organization/objects/organization.js deleted file mode 100644 index 7601a6284a..0000000000 --- a/api-js/src/organization/objects/organization.js +++ /dev/null @@ -1,151 +0,0 @@ -import { t } from '@lingui/macro' -import { - GraphQLBoolean, - GraphQLInt, - GraphQLObjectType, - GraphQLString, -} from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' - -import { organizationSummaryType } from './organization-summary' -import { nodeInterface } from '../../node' -import { Acronym, Slug } from '../../scalars' -import { affiliationUserOrder } from '../../affiliation/inputs' -import { affiliationConnection } from '../../affiliation/objects' -import { domainOrder } from '../../domain/inputs' -import { domainConnection } from '../../domain/objects' - -export const organizationType = new GraphQLObjectType({ - name: 'Organization', - fields: () => ({ - id: globalIdField('organization'), - acronym: { - type: Acronym, - description: 'The organizations acronym.', - resolve: ({ acronym }) => acronym, - }, - name: { - type: GraphQLString, - description: 'The full name of the organization.', - resolve: ({ name }) => name, - }, - slug: { - type: Slug, - description: 'Slugified name of the organization.', - resolve: ({ slug }) => slug, - }, - zone: { - type: GraphQLString, - description: 'The zone which the organization belongs to.', - resolve: ({ zone }) => zone, - }, - sector: { - type: GraphQLString, - description: 'The sector which the organization belongs to.', - resolve: ({ sector }) => sector, - }, - country: { - type: GraphQLString, - description: 'The country in which the organization resides.', - resolve: ({ country }) => country, - }, - province: { - type: GraphQLString, - description: 'The province in which the organization resides.', - resolve: ({ province }) => province, - }, - city: { - type: GraphQLString, - description: 'The city in which the organization resides.', - resolve: ({ city }) => city, - }, - verified: { - type: GraphQLBoolean, - description: 'Whether the organization is a verified organization.', - resolve: ({ verified }) => verified, - }, - summaries: { - type: organizationSummaryType, - description: - 'Summaries based on scan types that are preformed on the given organizations domains.', - resolve: ({ summaries }) => summaries, - }, - domainCount: { - type: GraphQLInt, - description: 'The number of domains associated with this organization.', - resolve: ({ domainCount }) => domainCount, - }, - domains: { - type: domainConnection.connectionType, - description: 'The domains which are associated with this organization.', - args: { - orderBy: { - type: domainOrder, - description: 'Ordering options for domain connections.', - }, - ownership: { - type: GraphQLBoolean, - description: - 'Limit domains to those that belong to an organization that has ownership.', - }, - search: { - type: GraphQLString, - description: 'String used to search for domains.', - }, - ...connectionArgs, - }, - resolve: async ( - { _id }, - args, - { loaders: { loadDomainConnectionsByOrgId } }, - ) => { - const connections = await loadDomainConnectionsByOrgId({ - orgId: _id, - ...args, - }) - return connections - }, - }, - affiliations: { - type: affiliationConnection.connectionType, - description: 'Organization affiliations to various users.', - args: { - orderBy: { - type: affiliationUserOrder, - description: 'Ordering options for affiliation connections.', - }, - search: { - type: GraphQLString, - description: 'String used to search for affiliated users.', - }, - ...connectionArgs, - }, - resolve: async ( - { _id }, - args, - { - i18n, - auth: { checkPermission }, - loaders: { loadAffiliationConnectionsByOrgId }, - }, - ) => { - const permission = await checkPermission({ orgId: _id }) - if (permission === 'admin' || permission === 'super_admin') { - const affiliations = await loadAffiliationConnectionsByOrgId({ - orgId: _id, - ...args, - }) - return affiliations - } - throw new Error( - i18n._( - t`Cannot query affiliations on organization without admin permission or higher.`, - ), - ) - }, - }, - }), - interfaces: [nodeInterface], - description: - 'Organization object containing information for a given Organization.', -}) diff --git a/api-js/src/organization/queries/find-my-organizations.js b/api-js/src/organization/queries/find-my-organizations.js deleted file mode 100644 index 88ce67b4ad..0000000000 --- a/api-js/src/organization/queries/find-my-organizations.js +++ /dev/null @@ -1,54 +0,0 @@ -import { GraphQLBoolean, GraphQLString } from 'graphql' -import { connectionArgs } from 'graphql-relay' - -import { organizationOrder } from '../inputs' -import { organizationConnection } from '../objects' - -export const findMyOrganizations = { - type: organizationConnection.connectionType, - description: 'Select organizations a user has access to.', - args: { - orderBy: { - type: organizationOrder, - description: 'Ordering options for organization connections', - }, - search: { - type: GraphQLString, - description: 'String argument used to search for organizations.', - }, - isAdmin: { - type: GraphQLBoolean, - description: 'Filter orgs based off of the user being an admin of them.', - }, - includeSuperAdminOrg: { - type: GraphQLBoolean, - description: - 'Filter org list to either include or exclude the super admin org.', - }, - ...connectionArgs, - }, - resolve: async ( - _, - args, - { - userKey, - auth: { checkSuperAdmin, userRequired, verifiedRequired }, - loaders: { loadOrgConnectionsByUserId }, - }, - ) => { - const user = await userRequired() - - verifiedRequired({ user }) - - const isSuperAdmin = await checkSuperAdmin() - - const orgConnections = await loadOrgConnectionsByUserId({ - isSuperAdmin, - ...args, - }) - - console.info(`User ${userKey} successfully retrieved their organizations.`) - - return orgConnections - }, -} diff --git a/api-js/src/organization/queries/find-organization-by-slug.js b/api-js/src/organization/queries/find-organization-by-slug.js deleted file mode 100644 index d878bbe952..0000000000 --- a/api-js/src/organization/queries/find-organization-by-slug.js +++ /dev/null @@ -1,64 +0,0 @@ -import { GraphQLNonNull } from 'graphql' -import { t } from '@lingui/macro' -import { Slug } from '../../scalars' - -const { organizationType } = require('../objects') - -export const findOrganizationBySlug = { - type: organizationType, - description: - 'Select all information on a selected organization that a user has access to.', - args: { - orgSlug: { - type: GraphQLNonNull(Slug), - description: - 'The slugified organization name you want to retrieve data for.', - }, - }, - resolve: async ( - _, - args, - { - i18n, - auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgBySlug }, - validators: { cleanseInput }, - }, - ) => { - // Get User - const user = await userRequired() - - verifiedRequired({ user }) - - // Cleanse input - const orgSlug = cleanseInput(args.orgSlug) - - // Retrieve organization by slug - const org = await loadOrgBySlug.load(orgSlug) - - if (typeof org === 'undefined') { - console.warn(`User ${user._key} could not retrieve organization.`) - throw new Error( - i18n._(t`No organization with the provided slug could be found.`), - ) - } - - // Check user permission for organization access - const permission = await checkPermission({ orgId: org._id }) - - if (!['super_admin', 'admin', 'user'].includes(permission)) { - console.warn(`User ${user._key} could not retrieve organization.`) - throw new Error( - i18n._( - t`Permission Denied: Could not retrieve specified organization.`, - ), - ) - } - - console.info( - `User ${user._key} successfully retrieved organization ${org._key}.`, - ) - org.id = org._key - return org - }, -} diff --git a/api-js/src/organization/queries/index.js b/api-js/src/organization/queries/index.js deleted file mode 100644 index 1b2380b3ac..0000000000 --- a/api-js/src/organization/queries/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './find-my-organizations' -export * from './find-organization-by-slug' diff --git a/api-js/src/scalars/__tests__/scalar-selector.test.js b/api-js/src/scalars/__tests__/scalar-selector.test.js deleted file mode 100644 index 121c88e3fd..0000000000 --- a/api-js/src/scalars/__tests__/scalar-selector.test.js +++ /dev/null @@ -1,117 +0,0 @@ -import { Kind } from 'graphql' -import { stringify } from 'jest-matcher-utils' -import { Selectors } from '../index' - -describe('given a selectors scalar', () => { - describe('serializing inputs', () => { - describe('given valid inputs', () => { - describe('given a valid selector', () => { - it('returns test selector', () => { - const testSelector = 'selector1._domainkey' - expect(Selectors.serialize(testSelector)).toEqual(testSelector) - }) - }) - describe('given an invalid selector', () => { - describe('selector contains string', () => { - it('throws an error', () => { - const testSelector = 'This is an invalid selector' - expect(() => Selectors.serialize(testSelector)).toThrow( - new TypeError(`Value is not a valid selector: ${testSelector}`), - ) - }) - }) - }) - }) - describe('given invalid inputs', () => { - ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { - expect(() => Selectors.serialize(invalidInput)).toThrow( - new TypeError(`Value is not a string: ${typeof invalidInput}`), - ) - }) - }) - }) - }) - describe('value parsing', () => { - describe('given valid inputs', () => { - describe('given a valid selector', () => { - it('returns test selector', () => { - const testSelector = 'selector1._domainkey' - expect(Selectors.parseValue(testSelector)).toEqual(testSelector) - }) - }) - describe('given an invalid selector', () => { - describe('selector contains string', () => { - it('throws an error', () => { - const testSelector = 'This is an invalid selector' - expect(() => Selectors.parseValue(testSelector)).toThrow( - new TypeError(`Value is not a valid selector: ${testSelector}`), - ) - }) - }) - }) - }) - describe('given invalid inputs', () => { - ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { - expect(() => Selectors.parseValue(invalidInput)).toThrow( - new TypeError(`Value is not a string: ${typeof invalidInput}`), - ) - }) - }) - }) - }) - describe('literal parsing', () => { - describe('given valid inputs', () => { - describe('given a valid selector', () => { - it('returns test selector', () => { - const testSelector = 'selector1._domainkey' - const testLiteral = { - kind: Kind.STRING, - value: testSelector, - } - expect(Selectors.parseLiteral(testLiteral, {})).toEqual(testSelector) - }) - }) - describe('given an invalid selector', () => { - describe('selector contains string', () => { - it('throws an error', () => { - const testSelector = 'This is an invalid selector' - const testLiteral = { - kind: Kind.STRING, - value: testSelector, - } - expect(() => Selectors.parseLiteral(testLiteral, {})).toThrow( - new TypeError(`Value is not a valid selector: ${testSelector}`), - ) - }) - }) - }) - }) - describe('given invalid inputs', () => { - ;[ - { - kind: Kind.FLOAT, - value: '5', - }, - { - kind: Kind.DOCUMENT, - }, - ].forEach((literal) => { - it(`throws an error when parsing invalid literal ${stringify( - literal, - )}`, () => { - expect(() => Selectors.parseLiteral(literal, {})).toThrow( - new TypeError( - `Can only validate strings as selectors but got a: ${literal.kind}`, - ), - ) - }) - }) - }) - }) -}) diff --git a/api-js/src/scalars/domain.js b/api-js/src/scalars/domain.js deleted file mode 100644 index dc4fd32123..0000000000 --- a/api-js/src/scalars/domain.js +++ /dev/null @@ -1,38 +0,0 @@ -import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' - -const validate = (value) => { - if (typeof value !== typeof 'string') { - throw new TypeError(`Value is not a string: ${typeof value}`) - } - - value = value.toLowerCase() - - const DOMAIN_REGEX = - /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ - - if (!DOMAIN_REGEX.test(value)) { - throw new TypeError(`Value is not a valid domain: ${value}`) - } - - return value -} - -export const Domain = new GraphQLScalarType({ - name: 'DomainScalar', - description: 'String that conforms to a domain structure.', - serialize: validate, - parseValue: validate, - - parseLiteral(ast) { - if (ast.kind !== Kind.STRING) { - throw new GraphQLError( - `Can only validate strings as domains but got a: ${ast.kind}`, - ) - } - return validate(ast.value) - }, -}) - -module.exports = { - Domain, -} diff --git a/api-js/src/scalars/index.js b/api-js/src/scalars/index.js deleted file mode 100644 index ea5b750222..0000000000 --- a/api-js/src/scalars/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export * from './domain' -export * from './organization-acronym' -export * from './selector' -export * from './slug' -export * from './year' diff --git a/api-js/src/scalars/organization-acronym.js b/api-js/src/scalars/organization-acronym.js deleted file mode 100644 index b74643af90..0000000000 --- a/api-js/src/scalars/organization-acronym.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' - -const validate = (value) => { - const ACRONYM_REGEX = /^[A-Z0-9_-]{1,50}$/ - - if (typeof value !== 'string') { - throw new TypeError(`Value is not string: ${typeof value}`) - } - - if (!ACRONYM_REGEX.test(value)) { - throw new TypeError(`Value is not a valid acronym: ${value}`) - } - return value -} - -export const Acronym = new GraphQLScalarType({ - name: 'Acronym', - description: - 'A field whose value is an upper case letter or an under score that has a length between 1 and 50.', - serialize: validate, - parseValue: validate, - - parseLiteral(ast) { - if (ast.kind !== Kind.STRING) { - throw new GraphQLError( - `Can only validate strings as acronyms but got a: ${ast.kind}`, - ) - } - return validate(ast.value) - }, -}) diff --git a/api-js/src/scalars/selector.js b/api-js/src/scalars/selector.js deleted file mode 100644 index bf59699419..0000000000 --- a/api-js/src/scalars/selector.js +++ /dev/null @@ -1,30 +0,0 @@ -import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' - -const validate = (value) => { - const SLUG_REGEX = /\w+\._domainkey/ - if (typeof value !== typeof 'string') { - throw new TypeError(`Value is not a string: ${typeof value}`) - } - if (!SLUG_REGEX.test(value)) { - throw new TypeError(`Value is not a valid selector: ${value}`) - } - - return value -} - -export const Selectors = new GraphQLScalarType({ - name: 'Selector', - description: - 'A field that conforms to a string, with strings ending in ._domainkey.', - serialize: validate, - parseValue: validate, - - parseLiteral(ast) { - if (ast.kind !== Kind.STRING) { - throw new GraphQLError( - `Can only validate strings as selectors but got a: ${ast.kind}`, - ) - } - return validate(ast.value) - }, -}) diff --git a/api-js/src/server.js b/api-js/src/server.js deleted file mode 100644 index 4228f4d314..0000000000 --- a/api-js/src/server.js +++ /dev/null @@ -1,149 +0,0 @@ -import cookieParser from 'cookie-parser' -import cors from 'cors' -import express from 'express' -import http from 'http' -import { ApolloServerPluginLandingPageGraphQLPlayground as enablePlayground } from 'apollo-server-core' -import { ApolloServer } from 'apollo-server-express' -import requestLanguage from 'express-request-language' -import { execute, subscribe, GraphQLSchema } from 'graphql' -import depthLimit from 'graphql-depth-limit' -import { createComplexityLimitRule } from 'graphql-validation-complexity' -import { SubscriptionServer } from 'subscriptions-transport-ws' -import { express as voyagerMiddleware } from 'graphql-voyager/middleware' - -import { createContext } from './create-context' -import { createQuerySchema } from './query' -import { createMutationSchema } from './mutation' -import { createSubscriptionSchema } from './subscription' -import { createI18n } from './create-i18n' -import { verifyToken, userRequired, verifiedRequired } from './auth' -import { loadUserByKey } from './user/loaders' -import { customOnConnect } from './on-connect' -import { arangodb } from 'arango-express' - -const createSchema = () => - new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - subscription: createSubscriptionSchema(), - }) - -const createValidationRules = ( - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, -) => { - return [ - depthLimit(maxDepth), - createComplexityLimitRule(complexityCost, { - scalarCost, - objectCost, - listFactor, - formatErrorMessage: (cost) => { - console.warn(`User attempted a costly request: ${cost}`) - return `Query error, query is too complex.` - }, - }), - ] -} - -export const Server = async ({ - arango = {}, - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, - tracing, - context = {}, -}) => { - const app = express() - - app.use('*', cors()) - - app.use(cookieParser()) - - app.use( - requestLanguage({ - languages: ['en', 'fr'], - }), - ) - - app.use(arangodb(arango)) - - app.use('/voyager', voyagerMiddleware({ endpointUrl: '/graphql' })) - - app.get('/alive', (_req, res) => { - res.json({ ok: 'yes' }) - }) - - app.get('/ready', (_req, res) => { - res.json({ ok: 'yes' }) - }) - - // default error handler - app.use(function (err, _req, res, _next) { - res.status(200).json({ - error: { - errors: [ - { - message: err, - locations: [ - { - line: 1, - column: 1, - }, - ], - }, - ], - }, - }) - }) - - const httpServer = http.createServer(app) - - const schema = createSchema() - - const server = new ApolloServer({ - schema, - context: createContext(context), - validationRules: createValidationRules( - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, - ), - introspection: true, - tracing, - plugins: [enablePlayground()], - }) - - await server.start() - server.applyMiddleware({ app }) - - SubscriptionServer.create( - { - schema, - execute, - subscribe, - onConnect: customOnConnect({ - createContext, - serverContext: context, - createI18n, - verifyToken, - userRequired, - loadUserByKey, - verifiedRequired, - }), - }, - { - server: httpServer, - path: server.graphqlPath, - }, - ) - - return httpServer -} diff --git a/api-js/src/subscription.js b/api-js/src/subscription.js deleted file mode 100644 index 23c2ed9d9f..0000000000 --- a/api-js/src/subscription.js +++ /dev/null @@ -1,14 +0,0 @@ -import { GraphQLObjectType } from 'graphql' - -import * as emailScanSubscriptions from './email-scan/subscriptions' -import * as webScanSubscriptions from './web-scan/subscriptions' - -export const createSubscriptionSchema = () => { - return new GraphQLObjectType({ - name: 'Subscription', - fields: () => ({ - ...emailScanSubscriptions, - ...webScanSubscriptions, - }), - }) -} diff --git a/api-js/src/summaries/loaders/__tests__/load-chart-summary-by-key.test.js b/api-js/src/summaries/loaders/__tests__/load-chart-summary-by-key.test.js deleted file mode 100644 index d5a6ad0aef..0000000000 --- a/api-js/src/summaries/loaders/__tests__/load-chart-summary-by-key.test.js +++ /dev/null @@ -1,222 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadChartSummaryByKey } from '../../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadChartSummaryByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.chartSummaries.save({ - _key: 'web', - total: 1000, - fail: 500, - pass: 500, - }) - await collections.chartSummaries.save({ - _key: 'mail', - total: 1000, - fail: 500, - pass: 500, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single summary', async () => { - const expectedCursor = await query` - FOR summary IN chartSummaries - FILTER summary._key == "web" - RETURN MERGE({ id: summary._key }, summary) - ` - const expectedSummary = await expectedCursor.next() - - const loader = loadChartSummaryByKey({ query, i18n }) - const webSummary = await loader.load('web') - - expect(webSummary).toEqual(expectedSummary) - }) - }) - describe('given multiple ids', () => { - it('returns multiple dkim scans', async () => { - const summaryKeys = [] - const expectedSummaries = [] - const expectedCursor = await query` - FOR summary IN chartSummaries - RETURN MERGE({ id: summary._key }, summary) - ` - - while (expectedCursor.hasMore) { - const tempSummary = await expectedCursor.next() - summaryKeys.push(tempSummary._key) - expectedSummaries.push(tempSummary) - } - - const loader = loadChartSummaryByKey({ query, i18n }) - const chartSummaries = await loader.loadMany(summaryKeys) - expect(chartSummaries).toEqual(expectedSummaries) - }) - }) - }) - describe('given an unsuccessful load', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadChartSummaryByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to load summary. Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadChartSummaryByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadChartSummaryByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to load summary. Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadChartSummaryByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadChartSummaryByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Impossible de charger le résumé. Veuillez réessayer.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadChartSummaryByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadChartSummaryByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Impossible de charger le résumé. Veuillez réessayer.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadChartSummaryByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/summaries/loaders/index.js b/api-js/src/summaries/loaders/index.js deleted file mode 100644 index b06b337eb6..0000000000 --- a/api-js/src/summaries/loaders/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './load-chart-summary-by-key' diff --git a/api-js/src/summaries/loaders/load-chart-summary-by-key.js b/api-js/src/summaries/loaders/load-chart-summary-by-key.js deleted file mode 100644 index 25691a7c74..0000000000 --- a/api-js/src/summaries/loaders/load-chart-summary-by-key.js +++ /dev/null @@ -1,35 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadChartSummaryByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - - try { - cursor = await query` - WITH chartSummaries - FOR summary IN chartSummaries - FILTER summary._key IN ${keys} - RETURN MERGE({ id: summary._key }, summary) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadChartSummaryByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to load summary. Please try again.`)) - } - - const summaryMap = {} - try { - await cursor.forEach((summary) => { - summaryMap[summary._key] = summary - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadChartSummaryByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to load summary. Please try again.`)) - } - - return keys.map((key) => summaryMap[key]) - }) diff --git a/api-js/src/summaries/objects/index.js b/api-js/src/summaries/objects/index.js deleted file mode 100644 index e86c987e36..0000000000 --- a/api-js/src/summaries/objects/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './categorized-summary' -export * from './summary-category' diff --git a/api-js/src/summaries/queries/__tests__/mail-summary.test.js b/api-js/src/summaries/queries/__tests__/mail-summary.test.js deleted file mode 100644 index 8654010984..0000000000 --- a/api-js/src/summaries/queries/__tests__/mail-summary.test.js +++ /dev/null @@ -1,232 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { loadChartSummaryByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given mailSummary query', () => { - let query, drop, truncate, schema, collections, i18n - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(async () => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - beforeEach(() => { - consoleOutput.length = 0 - }) - - describe('given successful mail summary retrieval', () => { - beforeAll(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.chartSummaries.save({ - _key: 'mail', - total: 1000, - fail: 500, - pass: 500, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - it('returns mail summary', async () => { - const response = await graphql( - schema, - ` - query { - mailSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: loadChartSummaryByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - mailSummary: { - total: 1000, - categories: [ - { - name: 'pass', - count: 500, - percentage: 50, - }, - { - name: 'fail', - count: 500, - percentage: 50, - }, - ], - }, - }, - } - expect(response).toEqual(expectedResponse) - }) - }) - - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given unsuccessful mail summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - mailSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError(`Unable to load mail summary. Please try again.`), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve mail summary.`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given unsuccessful mail summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - mailSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de charger le résumé du courrier. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve mail summary.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/summaries/queries/__tests__/web-summary.test.js b/api-js/src/summaries/queries/__tests__/web-summary.test.js deleted file mode 100644 index 65bc5b9296..0000000000 --- a/api-js/src/summaries/queries/__tests__/web-summary.test.js +++ /dev/null @@ -1,231 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { loadChartSummaryByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given webSummary query', () => { - let query, drop, truncate, schema, collections, i18n - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given successful web summary retrieval', () => { - beforeAll(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.chartSummaries.save({ - _key: 'web', - total: 1000, - fail: 500, - pass: 500, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - it('returns web summary', async () => { - const response = await graphql( - schema, - ` - query { - webSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: loadChartSummaryByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - webSummary: { - total: 1000, - categories: [ - { - name: 'pass', - count: 500, - percentage: 50, - }, - { - name: 'fail', - count: 500, - percentage: 50, - }, - ], - }, - }, - } - expect(response).toEqual(expectedResponse) - }) - }) - - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given unsuccessful web summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - webSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError(`Unable to load web summary. Please try again.`), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve web summary.`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given unsuccessful web summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - webSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de charger le résumé web. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve web summary.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/summaries/queries/index.js b/api-js/src/summaries/queries/index.js deleted file mode 100644 index 7d8abf431e..0000000000 --- a/api-js/src/summaries/queries/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './mail-summary' -export * from './web-summary' diff --git a/api-js/src/summaries/queries/mail-summary.js b/api-js/src/summaries/queries/mail-summary.js deleted file mode 100644 index a43eb8d5ce..0000000000 --- a/api-js/src/summaries/queries/mail-summary.js +++ /dev/null @@ -1,33 +0,0 @@ -import { categorizedSummaryType } from '../objects' -import { t } from '@lingui/macro' - -export const mailSummary = { - type: categorizedSummaryType, - description: 'Email summary computed values, used to build summary cards.', - resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { - const summary = await loadChartSummaryByKey.load('mail') - - if (typeof summary === 'undefined') { - console.warn(`User could not retrieve mail summary.`) - throw new Error(i18n._(t`Unable to load mail summary. Please try again.`)) - } - - const categories = [ - { - name: 'pass', - count: summary.pass, - percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), - }, - { - name: 'fail', - count: summary.fail, - percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), - }, - ] - - return { - categories, - total: summary.total, - } - }, -} diff --git a/api-js/src/summaries/queries/web-summary.js b/api-js/src/summaries/queries/web-summary.js deleted file mode 100644 index 2079b5d88b..0000000000 --- a/api-js/src/summaries/queries/web-summary.js +++ /dev/null @@ -1,34 +0,0 @@ -import { t } from '@lingui/macro' - -import { categorizedSummaryType } from '../objects' - -export const webSummary = { - type: categorizedSummaryType, - description: 'Web summary computed values, used to build summary cards.', - resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { - const summary = await loadChartSummaryByKey.load('web') - - if (typeof summary === 'undefined') { - console.warn(`User could not retrieve web summary.`) - throw new Error(i18n._(t`Unable to load web summary. Please try again.`)) - } - - const categories = [ - { - name: 'pass', - count: summary.pass, - percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), - }, - { - name: 'fail', - count: summary.fail, - percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), - }, - ] - - return { - categories, - total: summary.total, - } - }, -} diff --git a/api-js/src/user/index.js b/api-js/src/user/index.js deleted file mode 100644 index 1b5bd9dcd0..0000000000 --- a/api-js/src/user/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export * from './loaders' -export * from './mutations' -export * from './objects' -export * from './queries' -export * from './unions' diff --git a/api-js/src/user/loaders/index.js b/api-js/src/user/loaders/index.js deleted file mode 100644 index 1df3a835e9..0000000000 --- a/api-js/src/user/loaders/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './load-user-by-key' -export * from './load-user-by-username' diff --git a/api-js/src/user/mutations/__tests__/close-account.test.js b/api-js/src/user/mutations/__tests__/close-account.test.js deleted file mode 100644 index c46e8214e0..0000000000 --- a/api-js/src/user/mutations/__tests__/close-account.test.js +++ /dev/null @@ -1,5484 +0,0 @@ -import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { toGlobalId } from 'graphql-relay' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { checkSuperAdmin, userRequired } from '../../../auth' -import { loadOrgByKey } from '../../../organization/loaders' -import { loadUserByKey } from '../../../user/loaders' -import { cleanseInput } from '../../../validators' -import { createMutationSchema } from '../../../mutation' -import { createQuerySchema } from '../../../query' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the closeAccount mutation', () => { - let i18n, - query, - drop, - truncate, - schema, - collections, - transaction, - user, - org, - domain - - const consoleOutput = [] - const mockedConsole = (output) => consoleOutput.push(output) - beforeAll(() => { - console.info = mockedConsole - console.warn = mockedConsole - console.error = mockedConsole - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful closing of an account', () => { - beforeEach(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - afterEach(async () => { - await truncate() - await drop() - }) - describe('user is closing their own account', () => { - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - const dkim = await collections.dkim.save({ dkim: true }) - await collections.domainsDKIM.save({ - _from: domain._id, - _to: dkim._id, - }) - const dkimResult = await collections.dkimResults.save({ - dkimResult: true, - }) - await collections.dkimToDkimResults.save({ - _from: dkim._id, - _to: dkimResult._id, - }) - const dmarc = await collections.dmarc.save({ dmarc: true }) - await collections.domainsDMARC.save({ - _from: domain._id, - _to: dmarc._id, - }) - const spf = await collections.spf.save({ spf: true }) - await collections.domainsSPF.save({ - _from: domain._id, - _to: spf._id, - }) - const https = await collections.https.save({ https: true }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, - }) - const ssl = await collections.ssl.save({ ssl: true }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, - }) - const dmarcSummary = await collections.dmarcSummaries.save({ - dmarcSummary: true, - }) - await collections.domainsToDmarcSummaries.save({ - _from: domain._id, - _to: dmarcSummary._id, - }) - }) - describe('user is an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: true, - }) - }) - describe('org is owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - it('removes ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) - }) - describe('org is not owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - it('does not remove ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - }) - }) - describe('org is the only one claiming a domain', () => { - it('removes dkimResult data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - }) - it('removes scan data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) - }) - it('removes claims', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - const testOrg = await testClaimCursor.next() - expect(testOrg).toEqual(undefined) - }) - it('removes domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.claims.save({ - _from: org2._id, - _to: domain._id, - }) - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes the claim', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = await query` - FOR claim IN claims - OPTIONS { waitForSync: true } - FILTER claim._from == ${org._id} - RETURN claim - ` - const testClaim = await testClaimCursor.next() - expect(testClaim).toEqual(undefined) - }) - }) - it('removes affiliated users and org', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = await query` - FOR org IN organizations - OPTIONS { waitForSync: true } - RETURN org - ` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - describe('user belongs to multiple orgs', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes requesting users remaining affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - }) - describe('user is not an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes the users affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Successfully closed account.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Le compte a été fermé avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - it('closes the users account', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - - const testUserCursor = - await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - const testUser = await testUserCursor.next() - expect(testUser).toEqual(undefined) - }) - }) - describe('super admin is closing another users account', () => { - let superAdmin, superAdminOrg - beforeEach(async () => { - superAdmin = await collections.users.save({ - userName: 'super.admin@istio.actually.exists', - emailValidated: true, - }) - superAdminOrg = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'super-admin', - acronym: 'SA', - name: 'Super Admin', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'super-admin', - acronym: 'SA', - name: 'Super Admin', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: superAdminOrg._id, - _to: superAdmin._id, - permission: 'super_admin', - owner: false, - }) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - const dkim = await collections.dkim.save({ dkim: true }) - await collections.domainsDKIM.save({ - _from: domain._id, - _to: dkim._id, - }) - const dkimResult = await collections.dkimResults.save({ - dkimResult: true, - }) - await collections.dkimToDkimResults.save({ - _from: dkim._id, - _to: dkimResult._id, - }) - const dmarc = await collections.dmarc.save({ dmarc: true }) - await collections.domainsDMARC.save({ - _from: domain._id, - _to: dmarc._id, - }) - const spf = await collections.spf.save({ spf: true }) - await collections.domainsSPF.save({ - _from: domain._id, - _to: spf._id, - }) - const https = await collections.https.save({ https: true }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, - }) - const ssl = await collections.ssl.save({ ssl: true }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, - }) - const dmarcSummary = await collections.dmarcSummaries.save({ - dmarcSummary: true, - }) - await collections.domainsToDmarcSummaries.save({ - _from: domain._id, - _to: dmarcSummary._id, - }) - }) - describe('user is an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: true, - }) - }) - describe('org is owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - it('removes ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) - }) - describe('org is not owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - it('does not remove ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - }) - }) - describe('org is the only one claiming a domain', () => { - it('removes dkimResult data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - }) - it('removes scan data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) - }) - it('removes claims', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - const testOrg = await testClaimCursor.next() - expect(testOrg).toEqual(undefined) - }) - it('removes domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.claims.save({ - _from: org2._id, - _to: domain._id, - }) - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes the claim', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = await query` - FOR claim IN claims - OPTIONS { waitForSync: true } - FILTER claim._from == ${org._id} - RETURN claim - ` - const testClaim = await testClaimCursor.next() - expect(testClaim).toEqual(undefined) - }) - }) - describe('user belongs to multiple orgs', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes requesting users remaining affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - FILTER aff._from != ${superAdminOrg._id} - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - it('removes affiliated users and org', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - FILTER aff._from != ${superAdminOrg._id} - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = await query` - FOR org IN organizations - OPTIONS { waitForSync: true } - FILTER org._key != ${superAdminOrg._key} - RETURN org - ` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('user is not an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes the users affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - FILTER aff._from != ${superAdminOrg._id} - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Successfully closed account.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Le compte a été fermé avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - it('closes the users account', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) - - await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - - const testUserCursor = await query` - FOR user IN users - OPTIONS { waitForSync: true } - FILTER user.userName != "super.admin@istio.actually.exists" - RETURN user - ` - const testUser = await testUserCursor.next() - expect(testUser).toEqual(undefined) - }) - }) - }) - - describe('given an unsuccessful closing of an account', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to close another users account', () => { - describe('requesting user is not a super admin', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ _key: '123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - "Permission error: Unable to close other user's account.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, - ]) - }) - }) - describe('requested user is undefined', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ _key: '123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - 'Unable to close account of an undefined user.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requested user is undefined.`, - ]) - }) - }) - }) - describe('database error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when gathering domain claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) - }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) - }) - describe('when getting claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) - }) - }) - describe('trx step error occurs', () => { - describe('domain is only claimed by one org', () => { - describe('when removing dmarc summary info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dmarc summaries when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownerships when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing dkimResult data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dkimResults when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing scan info when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing domain info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domains and claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - }) - describe('domain is claimed by multiple orgs', () => { - describe('when removing domain claim', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - }) - describe('when removing ownership orgs, and affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing the orgs remaining affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownership org and users affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing the users affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing the user', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - }) - describe('trx commit error occurs', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn().mockRejectedValue(new Error('trx commit error')), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to close account: users/123: Error: trx commit error`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to close another users account', () => { - describe('requesting user is not a super admin', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ _key: '123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, - ]) - }) - }) - describe('requested user is undefined', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ _key: '123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: { cleanseInput }, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - "Impossible de fermer le compte d'un utilisateur non défini.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requested user is undefined.`, - ]) - }) - }) - }) - describe('database error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when gathering domain claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) - }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) - }) - describe('when getting claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) - }) - }) - describe('trx step error occurs', () => { - describe('domain is only claimed by one org', () => { - describe('when removing dmarc summary info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dmarc summaries when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownerships when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing dkimResult data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dkimResults when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing scan info when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing domain info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 1 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domains and claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - }) - describe('domain is claimed by multiple orgs', () => { - describe('when removing domain claim', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - }) - describe('when removing ownership orgs, and affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing the orgs remaining affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownership org and users affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing the users affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing the user', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - }) - describe('trx commit error occurs', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{ count: 2 }]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn().mockRejectedValue(new Error('trx commit error')), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to close account: users/123: Error: trx commit error`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/user/mutations/__tests__/send-email-verification.test.js b/api-js/src/user/mutations/__tests__/send-email-verification.test.js deleted file mode 100644 index 5c3388c1e0..0000000000 --- a/api-js/src/user/mutations/__tests__/send-email-verification.test.js +++ /dev/null @@ -1,365 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import bcrypt from 'bcryptjs' -import { graphql, GraphQLSchema } from 'graphql' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { cleanseInput } from '../../../validators' -import { loadUserByUserName } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env -const mockNotify = jest.fn() - -describe('user send password reset email', () => { - - let query, drop, truncate, collections, schema, request, i18n - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - request = { - protocol: 'https', - get: (text) => text, - } - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('successfully sends verification email', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users preferred language is english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }) - }) - it('returns status text', async () => { - const response = await graphql( - schema, - ` - mutation { - sendEmailVerification( - input: { userName: "test.account@istio.actually.exists" } - ) { - status - } - } - `, - null, - { - i18n, - request, - query, - auth: { - bcrypt, - tokenize: jest.fn().mockReturnValue('token'), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const expectedResult = { - data: { - sendEmailVerification: { - status: - 'If an account with this username is found, an email verification link will be found in your inbox.', - }, - }, - } - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(response).toEqual(expectedResult) - expect(mockNotify).toHaveBeenCalledWith({ - user, - verifyUrl, - }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully sent a verification email.`, - ]) - }) - }) - describe('users preferred language is french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - }) - it('returns status text', async () => { - const response = await graphql( - schema, - ` - mutation { - sendEmailVerification( - input: { userName: "test.account@istio.actually.exists" } - ) { - status - } - } - `, - null, - { - i18n, - request, - query, - auth: { - bcrypt, - tokenize: jest.fn().mockReturnValue('token'), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const expectedResult = { - data: { - sendEmailVerification: { - status: - "Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.", - }, - }, - } - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(response).toEqual(expectedResult) - expect(mockNotify).toHaveBeenCalledWith({ - user, - verifyUrl, - }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully sent a verification email.`, - ]) - }) - }) - }) - describe('unsuccessfully sends verification email', () => { - describe('users preferred language is english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('no user associated with account', () => { - it('returns status text', async () => { - const response = await graphql( - schema, - ` - mutation { - sendEmailVerification( - input: { - userName: "test.account@istio.does.not.actually.exists" - } - ) { - status - } - } - `, - null, - { - i18n, - request, - query, - auth: { - bcrypt, - tokenize: jest.fn().mockReturnValue('token'), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const expectedResult = { - data: { - sendEmailVerification: { - status: - 'If an account with this username is found, an email verification link will be found in your inbox.', - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `A user attempted to send a verification email for test.account@istio.does.not.actually.exists but no account is affiliated with this user name.`, - ]) - }) - }) - }) - describe('users preferred language is french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('no user associated with account', () => { - it('returns status text', async () => { - const response = await graphql( - schema, - ` - mutation { - sendEmailVerification( - input: { - userName: "test.account@istio.does.not.actually.exists" - } - ) { - status - } - } - `, - null, - { - i18n, - request, - query, - auth: { - bcrypt, - tokenize: jest.fn().mockReturnValue('token'), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const expectedResult = { - data: { - sendEmailVerification: { - status: - "Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.", - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `A user attempted to send a verification email for test.account@istio.does.not.actually.exists but no account is affiliated with this user name.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/user/mutations/__tests__/sign-up.test.js b/api-js/src/user/mutations/__tests__/sign-up.test.js deleted file mode 100644 index e415a1f5f5..0000000000 --- a/api-js/src/user/mutations/__tests__/sign-up.test.js +++ /dev/null @@ -1,3803 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import bcrypt from 'bcryptjs' -import { graphql, GraphQLError, GraphQLSchema } from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' -import { v4 as uuidv4 } from 'uuid' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { tokenize, verifyToken } from '../../../auth' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { cleanseInput } from '../../../validators' -import { loadUserByUserName, loadUserByKey } from '../../loaders' -import { loadOrgByKey } from '../../../organization/loaders' - -const { DB_PASS: rootPass, DB_URL: url, REFRESH_TOKEN_EXPIRY } = process.env - -describe('testing user sign up', () => { - let query, - drop, - truncate, - collections, - transaction, - schema, - i18n, - mockTokenize, - mockNotify, - request - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - mockTokenize = jest.fn().mockReturnValue('token') - request = { - protocol: 'https', - get: (text) => text, - } - }) - beforeEach(() => { - mockNotify = jest.fn() - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful sign up', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a successful sign up', () => { - describe('when user is not signing up without an invite token', () => { - describe('user has rememberMe disabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - response: mockedResponse, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const users = await cursor.all() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', users[0]._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'ENGLISH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - expires: 0, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - describe('user has rememberMe enabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - rememberMe: true - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - response: mockedResponse, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const users = await cursor.all() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', users[0]._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'ENGLISH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - }) - describe('when the user is signing up with an invite token', () => { - let org, token - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: org._key, - requestedRole: 'admin', - }, - }) - }) - describe('user has rememberMe disabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - response: mockedResponse, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'ENGLISH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - expires: 0, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('creates affiliation', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const affiliationCursor = await query` - FOR affiliation IN affiliations - FILTER affiliation._to == ${user._id} - RETURN affiliation - ` - const checkAffiliation = await affiliationCursor.next() - - const expectedAffiliation = { - _from: org._id, - _to: user._id, - permission: 'admin', - } - - expect(checkAffiliation).toMatchObject(expectedAffiliation) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - describe('user has rememberMe enabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - rememberMe: true - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - response: mockedResponse, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'ENGLISH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('creates affiliation', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const affiliationCursor = await query` - FOR affiliation IN affiliations - FILTER affiliation._to == ${user._id} - RETURN affiliation - ` - const checkAffiliation = await affiliationCursor.next() - - const expectedAffiliation = { - _from: org._id, - _to: user._id, - permission: 'admin', - } - - expect(checkAffiliation).toMatchObject(expectedAffiliation) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given successful sign up', () => { - describe('when user is not signing up without an invite token', () => { - describe('user has rememberMe disabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - response: mockedResponse, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'FRENCH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - expires: 0, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - describe('user has rememberMe enabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - rememberMe: true - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - response: mockedResponse, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'FRENCH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - }) - describe('when the user is signing up with an invite token', () => { - let org, token - beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: org._key, - requestedRole: 'admin', - }, - }) - }) - describe('user has rememberMe disabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - response: mockedResponse, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'FRENCH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - expires: 0, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('creates affiliation', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const affiliationCursor = await query` - FOR affiliation IN affiliations - FILTER affiliation._to == ${user._id} - RETURN affiliation - ` - const checkAffiliation = await affiliationCursor.next() - - const expectedAffiliation = { - _from: org._id, - _to: user._id, - permission: 'admin', - } - - expect(checkAffiliation).toMatchObject(expectedAffiliation) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - describe('user has rememberMe enabled', () => { - it('returns auth result with user info', async () => { - const mockedCookie = jest.fn() - const mockedResponse = { cookie: mockedCookie } - - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - rememberMe: true - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - response: mockedResponse, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const expectedResult = { - data: { - signUp: { - result: { - authToken: 'token', - user: { - id: `${toGlobalId('user', user._key)}`, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'FRENCH', - phoneValidated: false, - emailValidated: false, - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists successfully created a new account.', - ]) - }) - it('creates affiliation', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const affiliationCursor = await query` - FOR affiliation IN affiliations - FILTER affiliation._to == ${user._id} - RETURN affiliation - ` - const checkAffiliation = await affiliationCursor.next() - - const expectedAffiliation = { - _from: org._id, - _to: user._id, - permission: 'admin', - } - - expect(checkAffiliation).toMatchObject(expectedAffiliation) - }) - it('sends verification email', async () => { - await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: ENGLISH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - request, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n: {} }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const user = await loadUserByUserName({ - query, - userKey: '1', - i18n: {}, - }).load('test.account@istio.actually.exists') - - const verifyUrl = `https://${request.get('host')}/validate/token` - - expect(mockNotify).toHaveBeenCalledWith({ - user: user, - verifyUrl, - }) - }) - }) - }) - }) - }) - }) - describe('given an unsuccessful sign up', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('when the password is not strong enough', () => { - it('returns a password too short error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "123" - confirmPassword: "123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: 'Password does not meet requirements.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up but did not meet requirements.', - ]) - }) - }) - describe('when the passwords do not match', () => { - it('returns a password not matching error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "321drowssaptset" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: 'Passwords do not match.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up but passwords do not match.', - ]) - }) - }) - describe('when the user name (email) already in use', () => { - it('returns an email already in use error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - phoneValidated: false, - emailValidated: false, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: 'Email already in use.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up, however there is already an account in use with that email.', - ]) - }) - }) - describe('user is signing up with invite token', () => { - describe('when the invite token user name and submitted user name do not match', () => { - let token - beforeEach(() => { - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: '123', - requestedRole: 'admin', - }, - }) - }) - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test@email.ca" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ - next: jest.fn(), - }), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: - 'Unable to sign up, please contact org admin for a new invite.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test@email.ca attempted to sign up with an invite token, however emails do not match.', - ]) - }) - }) - describe('when the invite token org key is not a defined org', () => { - let token - beforeEach(() => { - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: '456', - requestedRole: 'admin', - }, - }) - }) - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ - next: jest.fn(), - }), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: - 'Unable to sign up, please contact org admin for a new invite.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists attempted to sign up with an invite token, however the org could not be found.', - ]) - }) - }) - }) - describe('given a cursor error', () => { - describe('when gathering inserted user', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ - next: jest.fn().mockRejectedValue('Cursor Error'), - }), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Cursor error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Cursor Error', - ]) - }) - }) - }) - describe('given a transaction error', () => { - describe('when inserting user', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue('Transaction Step Error'), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Transaction Step Error', - ]) - }) - }) - describe('when inserting affiliation', () => { - let token - beforeEach(() => { - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: '123', - requestedRole: 'admin', - }, - }) - }) - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ next: jest.fn() }) - .mockRejectedValue('Transaction Step Error'), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, assigning affiliation: Transaction Step Error', - ]) - }) - }) - describe('when committing transaction', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ next: jest.fn() }), - commit: jest - .fn() - .mockRejectedValue('Transaction Commit Error'), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Transaction commit error occurred while user: test.account@istio.actually.exists attempted to sign up: Transaction Commit Error', - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('when the password is not strong enough', () => { - it('returns a password too short error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "123" - confirmPassword: "123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: 'Le mot de passe ne répond pas aux exigences.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up but did not meet requirements.', - ]) - }) - }) - describe('when the passwords do not match', () => { - it('returns a password not matching error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "321drowssaptset" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: 'Les mots de passe ne correspondent pas.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up but passwords do not match.', - ]) - }) - }) - describe('when the user name (email) already in use', () => { - it('returns an email already in use error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - phoneValidated: false, - emailValidated: false, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: 'Courriel déjà utilisé.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists tried to sign up, however there is already an account in use with that email.', - ]) - }) - }) - describe('user is signing up with invite token', () => { - describe('when the invite token user name and submitted user name do not match', () => { - let token - beforeEach(() => { - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: '123', - requestedRole: 'admin', - }, - }) - }) - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test@email.ca" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ - next: jest.fn(), - }), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: - "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test@email.ca attempted to sign up with an invite token, however emails do not match.', - ]) - }) - }) - describe('when the invite token org key is not a defined org', () => { - let token - beforeEach(() => { - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: '456', - requestedRole: 'admin', - }, - }) - }) - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ - next: jest.fn(), - }), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = { - data: { - signUp: { - result: { - code: 400, - description: - "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - 'User: test.account@istio.actually.exists attempted to sign up with an invite token, however the org could not be found.', - ]) - }) - }) - }) - describe('given a cursor error', () => { - describe('when gathering inserted user', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ - next: jest.fn().mockRejectedValue('Cursor Error'), - }), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Cursor error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Cursor Error', - ]) - }) - }) - }) - describe('given a transaction error', () => { - describe('when inserting user', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue('Transaction Step Error'), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Transaction Step Error', - ]) - }) - }) - describe('when inserting affiliation', () => { - let token - beforeEach(() => { - token = tokenize({ - parameters: { - userName: 'test.account@istio.actually.exists', - orgKey: '123', - requestedRole: 'admin', - }, - }) - }) - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - signUpToken: "${token}" - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ next: jest.fn() }) - .mockRejectedValue('Transaction Step Error'), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, assigning affiliation: Transaction Step Error', - ]) - }) - }) - describe('when committing transaction', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - signUp( - input: { - displayName: "Test Account" - userName: "test.account@istio.actually.exists" - password: "testpassword123" - confirmPassword: "testpassword123" - preferredLang: FRENCH - } - ) { - result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } - } - ... on SignUpError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({ next: jest.fn() }), - commit: jest - .fn() - .mockRejectedValue('Transaction Commit Error'), - }), - uuidv4, - auth: { - bcrypt, - tokenize: mockTokenize, - verifyToken: verifyToken({ i18n }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }), - }, - loadUserByKey: { - load: jest.fn(), - }, - }, - notify: { - sendVerificationEmail: mockNotify, - }, - }, - ) - - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] - - expect(response.errors).toEqual(error) - - expect(consoleOutput).toEqual([ - 'Transaction commit error occurred while user: test.account@istio.actually.exists attempted to sign up: Transaction Commit Error', - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/user/mutations/__tests__/update-user-profile.test.js b/api-js/src/user/mutations/__tests__/update-user-profile.test.js deleted file mode 100644 index 9e825b3412..0000000000 --- a/api-js/src/user/mutations/__tests__/update-user-profile.test.js +++ /dev/null @@ -1,2389 +0,0 @@ -import bcrypt from 'bcryptjs' -import crypto from 'crypto' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { cleanseInput } from '../../../validators' -import { tokenize, userRequired } from '../../../auth' -import { loadUserByUserName, loadUserByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url, CIPHER_KEY } = process.env - -describe('authenticate user account', () => { - let query, drop, truncate, collections, transaction, schema, i18n - - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - // Create GQL Schema - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful update', () => { - beforeAll(async () => { - // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'french', - tfaSendMethod: 'none', - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user updates their display name', () => { - it('returns a successful status message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { displayName: "John Doe" }) { - result { - ... on UpdateUserProfileResult { - status - user { - displayName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - displayName: 'John Doe', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user updates their user name', () => { - it('returns a successful status message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - userName: 'john.doe@istio.actually.works', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - it('sends new verify email link', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const mockNotify = jest.fn() - - await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - auth: { - bcrypt, - tokenize: jest.fn().mockReturnValue('token'), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: mockNotify }, - }, - ) - - const updatedCursor = await query` - FOR user IN users - FILTER user.userName == "john.doe@istio.actually.works" - RETURN user - ` - const updatedUser = await updatedCursor.next() - - expect(mockNotify).toBeCalledWith({ - verifyUrl: 'https://domain.ca/validate/token', - user: { - id: updatedUser._key, - _type: 'user', - ...updatedUser, - }, - }) - }) - describe('user is email validated', () => { - it('sets emailValidated to false', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - UPDATE user._key WITH { - emailValidated: true, - } IN users - RETURN NEW - ` - const user = await cursor.next() - - await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const checkCursor = await query` - FOR user IN users - RETURN user - ` - const checkUser = await checkCursor.next() - - expect(checkUser.emailValidated).toBeFalsy() - }) - }) - describe('user is not email validated', () => { - it('does not change emailValidated value', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - UPDATE user._key WITH { - emailValidated: false, - } IN users - RETURN NEW - ` - const user = await cursor.next() - - await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const checkCursor = await query` - FOR user IN users - RETURN user - ` - const checkUser = await checkCursor.next() - - expect(checkUser.emailValidated).toBeFalsy() - }) - }) - }) - describe('user updates their preferred language', () => { - it('returns a successful status message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { preferredLang: ENGLISH }) { - result { - ... on UpdateUserProfileResult { - status - user { - preferredLang - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - preferredLang: 'ENGLISH', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user attempts to update their tfa send method', () => { - describe('user attempts to set to phone', () => { - describe('user is phone validated', () => { - beforeEach(async () => { - await truncate() - - const updatedPhoneDetails = { - iv: crypto.randomBytes(12).toString('hex'), - } - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(updatedPhoneDetails.iv, 'hex'), - { authTagLength: 16 }, - ) - let encrypted = cipher.update('+12345678998', 'utf8', 'hex') - encrypted += cipher.final('hex') - - updatedPhoneDetails.phoneNumber = encrypted - updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') - - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - phoneValidated: true, - tfaSendMethod: 'none', - phoneDetails: updatedPhoneDetails, - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: PHONE }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'PHONE', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user is not phone validated', () => { - beforeEach(async () => { - await truncate() - - const updatedPhoneDetails = { - iv: crypto.randomBytes(12).toString('hex'), - } - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(updatedPhoneDetails.iv, 'hex'), - { authTagLength: 16 }, - ) - let encrypted = cipher.update('+12345678998', 'utf8', 'hex') - encrypted += cipher.final('hex') - - updatedPhoneDetails.phoneNumber = encrypted - updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') - - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - phoneValidated: false, - tfaSendMethod: 'none', - phoneDetails: updatedPhoneDetails, - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: PHONE }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'NONE', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - }) - describe('user attempts to set to email', () => { - describe('user is email validated', () => { - beforeEach(async () => { - await truncate() - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - emailValidated: true, - tfaSendMethod: 'none', - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: EMAIL }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'EMAIL', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user is not email validated', () => { - beforeEach(async () => { - await truncate() - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - emailValidated: false, - tfaSendMethod: 'none', - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: EMAIL }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'NONE', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - }) - describe('user attempts to set to none', () => { - beforeEach(async () => { - await truncate() - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - emailValidated: true, - tfaSendMethod: 'email', - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: NONE }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'NONE', - }, - status: 'Profile successfully updated.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user updates their display name', () => { - it('returns a successful status message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { displayName: "John Doe" }) { - result { - ... on UpdateUserProfileResult { - status - user { - displayName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - displayName: 'John Doe', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user updates their user name', () => { - describe('user updates their user name', () => { - it('returns a successful status message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - userName: 'john.doe@istio.actually.works', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - it('sends new verify email link', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const mockNotify = jest.fn() - - await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - auth: { - bcrypt, - tokenize: jest.fn().mockReturnValue('token'), - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: mockNotify }, - }, - ) - - const updatedCursor = await query` - FOR user IN users - FILTER user.userName == "john.doe@istio.actually.works" - RETURN user - ` - const updatedUser = await updatedCursor.next() - - expect(mockNotify).toBeCalledWith({ - verifyUrl: 'https://domain.ca/validate/token', - user: { - id: updatedUser._key, - _type: 'user', - ...updatedUser, - }, - }) - }) - describe('user is email validated', () => { - it('sets emailValidated to false', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - UPDATE user._key WITH { - emailValidated: true, - } IN users - RETURN NEW - ` - const user = await cursor.next() - - await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const checkCursor = await query` - FOR user IN users - RETURN user - ` - const checkUser = await checkCursor.next() - - expect(checkUser.emailValidated).toBeFalsy() - }) - }) - describe('user is not email validated', () => { - it('does not change emailValidated value', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - UPDATE user._key WITH { - emailValidated: false, - } IN users - RETURN NEW - ` - const user = await cursor.next() - - await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - request: { - get: jest.fn().mockReturnValue('domain.ca'), - }, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const checkCursor = await query` - FOR user IN users - RETURN user - ` - const checkUser = await checkCursor.next() - - expect(checkUser.emailValidated).toBeFalsy() - }) - }) - }) - }) - describe('user updates their preferred language', () => { - it('returns a successful status message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { preferredLang: ENGLISH }) { - result { - ... on UpdateUserProfileResult { - status - user { - preferredLang - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - preferredLang: 'ENGLISH', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user attempts to update their tfa send method', () => { - describe('user attempts to set to phone', () => { - describe('user is phone validated', () => { - beforeEach(async () => { - await truncate() - - const updatedPhoneDetails = { - iv: crypto.randomBytes(12).toString('hex'), - } - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(updatedPhoneDetails.iv, 'hex'), - { authTagLength: 16 }, - ) - let encrypted = cipher.update('+12345678998', 'utf8', 'hex') - encrypted += cipher.final('hex') - - updatedPhoneDetails.phoneNumber = encrypted - updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') - - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - phoneValidated: true, - tfaSendMethod: 'none', - phoneDetails: updatedPhoneDetails, - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: PHONE }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'PHONE', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user is not phone validated', () => { - beforeEach(async () => { - await truncate() - - const updatedPhoneDetails = { - iv: crypto.randomBytes(12).toString('hex'), - } - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(updatedPhoneDetails.iv, 'hex'), - { authTagLength: 16 }, - ) - let encrypted = cipher.update('+12345678998', 'utf8', 'hex') - encrypted += cipher.final('hex') - - updatedPhoneDetails.phoneNumber = encrypted - updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') - - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - phoneValidated: false, - tfaSendMethod: 'none', - phoneDetails: updatedPhoneDetails, - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: PHONE }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'NONE', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - }) - describe('user attempts to set to email', () => { - describe('user is email validated', () => { - beforeEach(async () => { - await truncate() - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - emailValidated: true, - tfaSendMethod: 'none', - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: EMAIL }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'EMAIL', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - describe('user is not email validated', () => { - beforeEach(async () => { - await truncate() - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - emailValidated: false, - tfaSendMethod: 'none', - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: EMAIL }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'NONE', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - }) - describe('user attempts to set to none', () => { - beforeEach(async () => { - await truncate() - await collections.users.save({ - displayName: 'Test Account', - userName: 'test.account@istio.actually.exists', - preferredLang: 'english', - emailValidated: true, - tfaSendMethod: 'email', - }) - }) - it('returns message, and the updated user info', async () => { - const cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - const user = await cursor.next() - - const response = await graphql( - schema, - ` - mutation { - updateUserProfile(input: { tfaSendMethod: NONE }) { - result { - ... on UpdateUserProfileResult { - status - user { - tfaSendMethod - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - bcrypt, - tokenize, - userRequired: userRequired({ - userKey: user._key, - loadUserByKey: loadUserByKey({ query }), - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: loadUserByUserName({ query }), - loadUserByKey: loadUserByKey({ query }), - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const expectedResponse = { - data: { - updateUserProfile: { - result: { - user: { - tfaSendMethod: 'NONE', - }, - status: 'Le profil a été mis à jour avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) - }) - }) - }) - }) - }) - describe('given an unsuccessful update', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to set email to one that is already in use', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - id - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - bcrypt, - tokenize, - userRequired: jest.fn().mockReturnValue({ - tfaSendMethod: 'none', - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const error = { - data: { - updateUserProfile: { - result: { - code: 400, - description: 'Username not available, please try another.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update their username, but the username is already in use.`, - ]) - }) - }) - describe('given a transaction step error', () => { - describe('when updating profile', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { - displayName: "John Smith" - userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH - } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - id - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), - }), - userKey: 123, - auth: { - bcrypt, - tokenize, - userRequired: jest.fn().mockReturnValue({ - tfaSendMethod: 'none', - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const error = [ - new GraphQLError('Unable to update profile. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when user: 123 attempted to update their profile: Error: Transaction step error`, - ]) - }) - }) - }) - describe('given a transaction step error', () => { - describe('when updating profile', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { - displayName: "John Smith" - userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH - } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - id - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), - }), - userKey: 123, - auth: { - bcrypt, - tokenize, - userRequired: jest.fn().mockReturnValue({ - tfaSendMethod: 'none', - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const error = [ - new GraphQLError('Unable to update profile. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to update their profile: Error: Transaction commit error`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to set email to one that is already in use', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - id - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn(), - userKey: 123, - auth: { - bcrypt, - tokenize, - userRequired: jest.fn().mockReturnValue({ - tfaSendMethod: 'none', - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue({}), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const error = { - data: { - updateUserProfile: { - result: { - code: 400, - description: - "Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update their username, but the username is already in use.`, - ]) - }) - }) - describe('given a transaction step error', () => { - describe('when updating profile', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { - displayName: "John Smith" - userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH - } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - id - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), - }), - userKey: 123, - auth: { - bcrypt, - tokenize, - userRequired: jest.fn().mockReturnValue({ - tfaSendMethod: 'none', - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le profil. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when user: 123 attempted to update their profile: Error: Transaction step error`, - ]) - }) - }) - }) - describe('given a transaction step error', () => { - describe('when updating profile', () => { - it('throws an error', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserProfile( - input: { - displayName: "John Smith" - userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH - } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - id - } - } - ... on UpdateUserProfileError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), - }), - userKey: 123, - auth: { - bcrypt, - tokenize, - userRequired: jest.fn().mockReturnValue({ - tfaSendMethod: 'none', - }), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByUserName: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - notify: { sendVerificationEmail: jest.fn() }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le profil. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to update their profile: Error: Transaction commit error`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/user/mutations/__tests__/verify-account.test.js b/api-js/src/user/mutations/__tests__/verify-account.test.js deleted file mode 100644 index 310573ddfd..0000000000 --- a/api-js/src/user/mutations/__tests__/verify-account.test.js +++ /dev/null @@ -1,1080 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { cleanseInput } from '../../../validators' -import { tokenize, verifyToken } from '../../../auth' -import { loadUserByKey } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('user send password reset email', () => { - let query, drop, truncate, collections, transaction, schema, request, i18n - const consoleOutput = [] - const mockedInfo = (output) => consoleOutput.push(output) - const mockedWarn = (output) => consoleOutput.push(output) - const mockedError = (output) => consoleOutput.push(output) - - beforeAll(() => { - console.info = mockedInfo - console.warn = mockedWarn - console.error = mockedError - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createMutationSchema(), - }) - request = { - protocol: 'https', - get: (text) => text, - } - }) - afterEach(() => { - consoleOutput.length = 0 - }) - - describe('given a successful validation', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }) - }) - it('returns a successful status message', async () => { - let cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - let user = await cursor.next() - - const token = tokenize({ parameters: { userKey: user._key } }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: user._key, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResult = { - data: { - verifyAccount: { - result: { - status: - 'Successfully email verified account, and set TFA send method to email.', - }, - }, - }, - } - - cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - user = await cursor.next() - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully email validated their account.`, - ]) - }) - it('sets emailValidated to true', async () => { - let cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - let user = await cursor.next() - - const token = tokenize({ parameters: { userKey: user._key } }) - - await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: user._key, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - user = await cursor.next() - - expect(user.emailValidated).toEqual(true) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - beforeEach(async () => { - await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }) - }) - it('returns a successful status message', async () => { - let cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - let user = await cursor.next() - - const token = tokenize({ parameters: { userKey: user._key } }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: user._key, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - const expectedResult = { - data: { - verifyAccount: { - result: { - status: - "Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.", - }, - }, - }, - } - - cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - user = await cursor.next() - - expect(response).toEqual(expectedResult) - expect(user.emailValidated).toEqual(true) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully email validated their account.`, - ]) - }) - it('sets emailValidated to true', async () => { - let cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - let user = await cursor.next() - - const token = tokenize({ parameters: { userKey: user._key } }) - - await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: user._key, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) - - cursor = await query` - FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" - RETURN user - ` - user = await cursor.next() - - expect(user.emailValidated).toEqual(true) - }) - }) - }) - describe('given an unsuccessful validation', () => { - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('userKey cannot be found in token parameters', () => { - it('returns an error message', async () => { - const token = tokenize({ - parameters: {}, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = { - data: { - verifyAccount: { - result: { - code: 400, - description: - 'Unable to verify account. Please request a new email.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, - ]) - }) - }) - describe('userKey in token is undefined', () => { - it('returns an error message', async () => { - const token = tokenize({ - parameters: { userKey: undefined }, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = { - data: { - verifyAccount: { - result: { - code: 400, - description: - 'Unable to verify account. Please request a new email.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, - ]) - }) - }) - describe('user cannot be found in db', () => { - it('returns an error message', async () => { - const token = tokenize({ - parameters: { userKey: 1 }, - }) - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 1, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = { - data: { - verifyAccount: { - result: { - code: 400, - description: - 'Unable to verify account. Please request a new email.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 1 attempted to verify account, however no account is associated with this id.`, - ]) - }) - }) - describe('transaction step error occurs', () => { - describe('when upserting validation', () => { - it('throws an error', async () => { - const token = tokenize({ - parameters: { userKey: 123 }, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue( - new Error('Transaction step error occurred.'), - ), - }), - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to verify account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when upserting email validation for user: 123: Error: Transaction step error occurred.`, - ]) - }) - }) - }) - describe('transaction commit error occurs', () => { - describe('when upserting validation', () => { - it('throws an error', async () => { - const token = tokenize({ - parameters: { userKey: 123 }, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue( - new Error('Transaction commit error occurred.'), - ), - }), - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError('Unable to verify account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when upserting email validation for user: 123: Error: Transaction commit error occurred.`, - ]) - }) - }) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('userKey cannot be found in token parameters', () => { - it('returns an error message', async () => { - const token = tokenize({ - parameters: {}, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = { - data: { - verifyAccount: { - result: { - code: 400, - description: - 'Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, - ]) - }) - }) - describe('userKey in token is undefined', () => { - it('returns an error message', async () => { - const token = tokenize({ - parameters: { userKey: undefined }, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = { - data: { - verifyAccount: { - result: { - code: 400, - description: - 'Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, - ]) - }) - }) - describe('user cannot be found in db', () => { - it('returns an error message', async () => { - const token = tokenize({ - parameters: { userKey: 1 }, - }) - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 1, - query, - collections, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = { - data: { - verifyAccount: { - result: { - code: 400, - description: - 'Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 1 attempted to verify account, however no account is associated with this id.`, - ]) - }) - }) - describe('transaction step error occurs', () => { - describe('when upserting validation', () => { - it('throws an error', async () => { - const token = tokenize({ - parameters: { userKey: 123 }, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue( - new Error('Transaction step error occurred.'), - ), - }), - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de vérifier le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when upserting email validation for user: 123: Error: Transaction step error occurred.`, - ]) - }) - }) - }) - describe('transaction commit error occurs', () => { - describe('when upserting validation', () => { - it('throws an error', async () => { - const token = tokenize({ - parameters: { userKey: 123 }, - }) - - const response = await graphql( - schema, - ` - mutation { - verifyAccount(input: { verifyTokenString: "${token}" }) { - result { - ... on VerifyAccountResult { - status - } - ... on VerifyAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - request, - userKey: 123, - query, - collections, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue( - new Error('Transaction commit error occurred.'), - ), - }), - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'english', - tfaValidated: false, - emailValidated: false, - }), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de vérifier le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when upserting email validation for user: 123: Error: Transaction commit error occurred.`, - ]) - }) - }) - }) - }) - }) -}) diff --git a/api-js/src/user/mutations/authenticate.js b/api-js/src/user/mutations/authenticate.js deleted file mode 100644 index c3270c2cb7..0000000000 --- a/api-js/src/user/mutations/authenticate.js +++ /dev/null @@ -1,180 +0,0 @@ -import { GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' -import { authenticateUnion } from '../unions' - -const { SIGN_IN_KEY, REFRESH_KEY, REFRESH_TOKEN_EXPIRY } = process.env - -export const authenticate = new mutationWithClientMutationId({ - name: 'Authenticate', - description: - 'This mutation allows users to give their credentials and retrieve a token that gives them access to restricted content.', - inputFields: () => ({ - authenticationCode: { - type: GraphQLNonNull(GraphQLInt), - description: 'Security code found in text msg, or email inbox.', - }, - authenticateToken: { - type: GraphQLNonNull(GraphQLString), - description: 'The JWT that is retrieved from the sign in mutation.', - }, - }), - outputFields: () => ({ - result: { - type: authenticateUnion, - description: - 'Authenticate union returning either a `authResult` or `authenticateError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - response, - query, - collections, - transaction, - uuidv4, - auth: { tokenize, verifyToken }, - loaders: { loadUserByKey }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Inputs - const authenticationCode = args.authenticationCode - const authenticationToken = cleanseInput(args.authenticateToken) - - // Gather token parameters - const tokenParameters = verifyToken({ - token: authenticationToken, - secret: String(SIGN_IN_KEY), - }) - - if ( - tokenParameters.userKey === 'undefined' || - typeof tokenParameters.userKey === 'undefined' - ) { - console.warn(`Authentication token does not contain the userKey`) - return { - _type: 'error', - code: 400, - description: i18n._(t`Token value incorrect, please sign in again.`), - } - } - - // Gather sign in user - const user = await loadUserByKey.load(tokenParameters.userKey) - - // Replace with userRequired() - if (typeof user === 'undefined') { - console.warn( - `User: ${tokenParameters.userKey} attempted to authenticate, no account is associated with this id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to authenticate. Please try again.`), - } - } - - // Check to see if security token matches the user submitted one - if (authenticationCode === user.tfaCode) { - const refreshId = uuidv4() - - const refreshInfo = { - refreshInfo: { - refreshId, - rememberMe: user.refreshInfo.rememberMe, - expiresAt: new Date( - new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), - }, - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Reset tfa code attempts, and set refresh code - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { - tfaCode: null, - refreshInfo: ${refreshInfo} - } - UPDATE { - tfaCode: null, - refreshInfo: ${refreshInfo} - } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when clearing tfa code and setting refresh id for user: ${user._key} during authentication: ${err}`, - ) - throw new Error(i18n._(t`Unable to authenticate. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${user._key} attempted to authenticate: ${err}`, - ) - throw new Error(i18n._(t`Unable to authenticate. Please try again.`)) - } - - const token = tokenize({ parameters: { userKey: user._key } }) - const refreshToken = tokenize({ - parameters: { userKey: user._key, uuid: refreshId }, - expPeriod: 168, - secret: String(REFRESH_KEY), - }) - - // if the user does not want to stay logged in, create http session cookie - let cookieData = { - httpOnly: true, - secure: true, - sameSite: true, - expires: 0, - } - - // if user wants to stay logged in create normal http cookie - if (user.refreshInfo.rememberMe) { - cookieData = { - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - httpOnly: true, - secure: true, - sameSite: true, - } - } - - response.cookie('refresh_token', refreshToken, cookieData) - - console.info( - `User: ${user._key} successfully authenticated their account.`, - ) - - return { - _type: 'authResult', - token, - user, - } - } else { - console.warn( - `User: ${user._key} attempted to authenticate their account, however the tfaCodes did not match.`, - ) - throw new Error(i18n._(t`Incorrect TFA code. Please sign in again.`)) - } - }, -}) diff --git a/api-js/src/user/mutations/close-account.js b/api-js/src/user/mutations/close-account.js deleted file mode 100644 index 265d45aa4d..0000000000 --- a/api-js/src/user/mutations/close-account.js +++ /dev/null @@ -1,511 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLID } from 'graphql' -import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' - -import { closeAccountUnion } from '../unions' - -export const closeAccount = new mutationWithClientMutationId({ - name: 'CloseAccount', - description: `This mutation allows a user to close their account, or a super admin to close another user's account.`, - inputFields: () => ({ - userId: { - type: GraphQLID, - description: 'The user id of a user you want to close the account of.', - }, - }), - outputFields: () => ({ - result: { - type: closeAccountUnion, - description: - '`CloseAccountUnion` returning either a `CloseAccountResult`, or `CloseAccountError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { checkSuperAdmin, userRequired }, - loaders: { loadUserByKey }, - validators: { cleanseInput }, - }, - ) => { - let submittedUserId - if (args?.userId) { - submittedUserId = fromGlobalId(cleanseInput(args.userId)).id - } - - const user = await userRequired() - - let userId = '' - if (submittedUserId) { - const permission = await checkSuperAdmin() - if (!permission) { - console.warn( - `User: ${user._key} attempted to close user: ${submittedUserId} account, but requesting user is not a super admin.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission error: Unable to close other user's account.`, - ), - } - } - - const checkUser = await loadUserByKey.load(submittedUserId) - if (typeof checkUser === 'undefined') { - console.warn( - `User: ${user._key} attempted to close user: ${submittedUserId} account, but requested user is undefined.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to close account of an undefined user.`), - } - } - userId = checkUser._id - } else { - userId = user._id - } - - // check to see if user owns any orgs - let orgOwnerAffiliationCursor - try { - orgOwnerAffiliationCursor = await query` - WITH users, affiliations, organizations - FOR v, e IN 1..1 INBOUND ${userId} affiliations - FILTER e.owner == true - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred when getting affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - let orgOwnerAffiliationCheck - try { - orgOwnerAffiliationCheck = await orgOwnerAffiliationCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Trans action - const trx = await transaction(collectionStrings) - - // loop through each found org - for (const affiliation of orgOwnerAffiliationCheck) { - let dmarcSummaryCheckCursor - try { - dmarcSummaryCheckCursor = await query` - WITH domains, ownership, organizations - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} ownership - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred when getting ownership info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - let dmarcSummaryCheckList - try { - dmarcSummaryCheckList = await dmarcSummaryCheckCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting ownership info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - // remove dmarc summary related things - for (const ownership of dmarcSummaryCheckList) { - try { - await trx.step( - () => query` - WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries - LET dmarcSummaryEdges = ( - FOR v, e IN 1..1 OUTBOUND ${ownership._to} domainsToDmarcSummaries - RETURN { edgeKey: e._key, dmarcSummaryId: e._to } - ) - LET removeDmarcSummaryEdges = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries - OPTIONS { waitForSync: true } - ) - LET removeDmarcSummary = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key - REMOVE key IN dmarcSummaries - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing dmarc summaries when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - try { - await trx.step( - () => query` - WITH ownership, organizations, domains - REMOVE ${ownership._key} IN ownership - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing ownerships when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - } - - let domainCountCursor - try { - domainCountCursor = await query` - WITH claims, domains, organizations - LET domainIds = ( - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} claims - RETURN e._to - ) - FOR domain IN domains - FILTER domain._id IN domainIds - LET count = LENGTH( - FOR v, e IN 1..1 INBOUND domain._id claims - RETURN 1 - ) - RETURN { - _id: domain._id, - _key: domain._key, - domain: domain.domain, - count - } - ` - } catch (err) { - console.error( - `Database error occurred when getting claim info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - let domainCountList - try { - domainCountList = await domainCountCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting claim info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - for (const domainObj of domainCountList) { - if (domainObj.count === 1) { - try { - await trx.step( - () => query` - WITH dkim, domains, domainsDKIM, dkimToDkimResults, dkimResults - LET dkimEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domainObj._id} domainsDKIM - RETURN { edgeKey: e._key, dkimId: e._to } - ) - FOR dkimEdge IN dkimEdges - LET dkimResultEdges = ( - FOR v, e IN 1..1 OUTBOUND dkimEdge.dkimId dkimToDkimResults - RETURN { edgeKey: e._key, dkimResultId: e._to } - ) - LET removeDkimResultEdges = ( - FOR dkimResultEdge IN dkimResultEdges - REMOVE dkimResultEdge.edgeKey IN dkimToDkimResults - OPTIONS { waitForSync: true } - ) - LET removeDkimResult = ( - FOR dkimResultEdge IN dkimResultEdges - REMOVE PARSE_IDENTIFIER(dkimResultEdge.dkimResultId).key - IN dkimResults OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing dkimResults when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to close account. Please try again.`), - ) - } - - try { - await Promise.all([ - trx.step( - () => query` - WITH dkim, domains, domainsDKIM - LET dkimEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domainObj._id} domainsDKIM - RETURN { edgeKey: e._key, dkimId: e._to } - ) - LET removeDkimEdges = ( - FOR dkimEdge IN dkimEdges - REMOVE dkimEdge.edgeKey IN domainsDKIM - OPTIONS { waitForSync: true } - ) - LET removeDkim = ( - FOR dkimEdge IN dkimEdges - REMOVE PARSE_IDENTIFIER(dkimEdge.dkimId).key - IN dkim OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => query` - WITH dmarc, domains, domainsDMARC - LET dmarcEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domainObj._id} domainsDMARC - RETURN { edgeKey: e._key, dmarcId: e._to } - ) - LET removeDmarcEdges = ( - FOR dmarcEdge IN dmarcEdges - REMOVE dmarcEdge.edgeKey IN domainsDMARC - OPTIONS { waitForSync: true } - ) - LET removeDmarc = ( - FOR dmarcEdge IN dmarcEdges - REMOVE PARSE_IDENTIFIER(dmarcEdge.dmarcId).key - IN dmarc OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => query` - WITH spf, domains, domainsSPF - LET spfEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domainObj._id} domainsSPF - RETURN { edgeKey: e._key, spfId: e._to } - ) - LET removeSpfEdges = ( - FOR spfEdge IN spfEdges - REMOVE spfEdge.edgeKey IN domainsSPF - OPTIONS { waitForSync: true } - ) - LET removeSpf = ( - FOR spfEdge IN spfEdges - REMOVE PARSE_IDENTIFIER(spfEdge.spfId).key - IN spf OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => query` - WITH https, domains, domainsHTTPS - LET httpsEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domainObj._id} domainsHTTPS - RETURN { edgeKey: e._key, httpsId: e._to } - ) - LET removeHttpsEdges = ( - FOR httpsEdge IN httpsEdges - REMOVE httpsEdge.edgeKey IN domainsHTTPS - OPTIONS { waitForSync: true } - ) - LET removeHttps = ( - FOR httpsEdge IN httpsEdges - REMOVE PARSE_IDENTIFIER(httpsEdge.httpsId).key - IN https OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => query` - WITH ssl, domains, domainsSSL - LET sslEdges = ( - FOR v, e IN 1..1 OUTBOUND ${domainObj._id} domainsSSL - RETURN { edgeKey: e._key, sslId: e._to } - ) - LET removeSslEdges = ( - FOR sslEdge IN sslEdges - REMOVE sslEdge.edgeKey IN domainsSSL - OPTIONS { waitForSync: true } - ) - LET removeSsl = ( - FOR sslEdge IN sslEdges - REMOVE PARSE_IDENTIFIER(sslEdge.sslId).key - IN ssl OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - ]) - } catch (err) { - console.error( - `Trx step error occurred when removing scan info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to close account. Please try again.`), - ) - } - - try { - await trx.step( - () => query` - WITH claims, domains, organizations - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} claims - FILTER e._to == ${domainObj._id} - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge IN domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - LET removeDomain = ( - FOR domainEdge IN domainEdges - REMOVE PARSE_IDENTIFIER(domainEdge.domainId).key - IN domains OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing domains and claims when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to close account. Please try again.`), - ) - } - } else { - try { - await trx.step( - () => query` - WITH claims, domains, organizations - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} claims - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge IN domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing domain claims when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to close account. Please try again.`), - ) - } - } - } - - // remove users affiliation - try { - await Promise.all([ - trx.step( - () => query` - WITH affiliations, organizations, users - LET userEdges = ( - FOR v, e IN 1..1 INBOUND ${affiliation._from} affiliations - RETURN { edgeKey: e._key, userKey: e._to } - ) - LET removeUserEdges = ( - FOR userEdge IN userEdges - REMOVE userEdge.userKey IN affiliations - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ), - trx.step( - () => query` - WITH organizations - REMOVE PARSE_IDENTIFIER(${affiliation._from}).key - IN organizations OPTIONS { waitForSync: true } - `, - ), - ]) - } catch (err) { - console.error( - `Trx step error occurred when removing ownership org and users affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - } - - try { - await trx.step( - () => query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 INBOUND ${userId} affiliations - REMOVE { _key: e._key } IN affiliations - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing users remaining affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - try { - await trx.step( - () => query` - WITH users - REMOVE PARSE_IDENTIFIER(${userId}).key - IN users OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - console.info( - `User: ${user._key} successfully closed user: ${userId} account.`, - ) - - return { - _type: 'regular', - status: i18n._(t`Successfully closed account.`), - } - }, -}) diff --git a/api-js/src/user/mutations/index.js b/api-js/src/user/mutations/index.js deleted file mode 100644 index 372334dd70..0000000000 --- a/api-js/src/user/mutations/index.js +++ /dev/null @@ -1,15 +0,0 @@ -export * from './authenticate' -export * from './close-account' -export * from './refresh-tokens' -export * from './remove-phone-number' -export * from './reset-password' -export * from './send-email-verification' -export * from './send-password-reset' -export * from './set-phone-number' -export * from './sign-in' -export * from './sign-out' -export * from './sign-up' -export * from './update-user-password' -export * from './update-user-profile' -export * from './verify-account' -export * from './verify-phone-number' diff --git a/api-js/src/user/mutations/refresh-tokens.js b/api-js/src/user/mutations/refresh-tokens.js deleted file mode 100644 index 0f99b3006a..0000000000 --- a/api-js/src/user/mutations/refresh-tokens.js +++ /dev/null @@ -1,190 +0,0 @@ -import { t } from '@lingui/macro' -import { mutationWithClientMutationId } from 'graphql-relay' - -import { refreshTokensUnion } from '../unions' - -const { REFRESH_TOKEN_EXPIRY, REFRESH_KEY } = process.env - -export const refreshTokens = new mutationWithClientMutationId({ - name: 'RefreshTokens', - description: - 'This mutation allows users to give their current auth token, and refresh token, and receive a freshly updated auth token.', - outputFields: () => ({ - result: { - type: refreshTokensUnion, - description: - 'Refresh tokens union returning either a `authResult` or `authenticateError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - _, - { - i18n, - response, - request, - query, - collections, - transaction, - uuidv4, - jwt, - moment, - auth: { tokenize }, - loaders: { loadUserByKey }, - }, - ) => { - // check uuid matches - let refreshToken - if ('refresh_token' in request.cookies) { - refreshToken = request.cookies.refresh_token - } - - if (typeof refreshToken === 'undefined') { - console.warn( - `User attempted to refresh tokens without refresh_token set.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to refresh tokens, please sign in.`), - } - } - - let decodedRefreshToken - try { - decodedRefreshToken = jwt.verify(refreshToken, REFRESH_KEY) - } catch (err) { - console.warn( - `User attempted to verify refresh token, however the token is invalid: ${err}`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to refresh tokens, please sign in.`), - } - } - - const { userKey, uuid } = decodedRefreshToken.parameters - - const user = await loadUserByKey.load(userKey) - - if (typeof user === 'undefined') { - console.warn( - `User: ${userKey} attempted to refresh tokens with an invalid user id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to refresh tokens, please sign in.`), - } - } - - // check to see if refresh token is expired - const currentTime = moment().format() - const expiryTime = moment(user.refreshInfo.expiresAt).format() - - if (moment(currentTime).isAfter(expiryTime)) { - console.warn( - `User: ${userKey} attempted to refresh tokens with an expired uuid.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to refresh tokens, please sign in.`), - } - } - - // check to see if token ids match - if (user.refreshInfo.refreshId !== uuid) { - console.warn( - `User: ${userKey} attempted to refresh tokens with non matching uuids.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to refresh tokens, please sign in.`), - } - } - - const newRefreshId = uuidv4() - - const refreshInfo = { - refreshId: newRefreshId, - rememberMe: user.refreshInfo.rememberMe, - expiresAt: new Date( - new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { refreshInfo: ${refreshInfo} } - UPDATE { refreshInfo: ${refreshInfo} } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when attempting to refresh tokens for user: ${userKey}: ${err}`, - ) - throw new Error(i18n._(t`Unable to refresh tokens, please sign in.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${userKey} attempted to refresh tokens: ${err}`, - ) - throw new Error(i18n._(t`Unable to refresh tokens, please sign in.`)) - } - - const newAuthToken = tokenize({ parameters: { userKey } }) - - console.info(`User: ${userKey} successfully refreshed their tokens.`) - - const newRefreshToken = tokenize({ - parameters: { userKey: user._key, uuid: newRefreshId }, - expPeriod: 168, - secret: String(REFRESH_KEY), - }) - - // if the user does not want to stay logged in, create http session cookie - let cookieData = { - httpOnly: true, - secure: true, - sameSite: true, - expires: 0, - } - - // if user wants to stay logged in create normal http cookie - if (user.refreshInfo.rememberMe) { - cookieData = { - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - httpOnly: true, - secure: true, - sameSite: true, - } - } - - response.cookie('refresh_token', newRefreshToken, cookieData) - - return { - _type: 'authResult', - token: newAuthToken, - user, - } - }, -}) diff --git a/api-js/src/user/mutations/remove-phone-number.js b/api-js/src/user/mutations/remove-phone-number.js deleted file mode 100644 index 5d7b5ca65b..0000000000 --- a/api-js/src/user/mutations/remove-phone-number.js +++ /dev/null @@ -1,84 +0,0 @@ -import { t } from '@lingui/macro' -import { mutationWithClientMutationId } from 'graphql-relay' - -import { removePhoneNumberUnion } from '../unions' - -export const removePhoneNumber = new mutationWithClientMutationId({ - name: 'RemovePhoneNumber', - description: - 'This mutation allows for users to remove a phone number from their account.', - outputFields: () => ({ - result: { - type: removePhoneNumberUnion, - description: - '`RemovePhoneNumberUnion` returning either a `RemovePhoneNumberResult`, or `RemovePhoneNumberError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - _args, - { i18n, collections, query, transaction, auth: { userRequired } }, - ) => { - // Get requesting user - const user = await userRequired() - - // Set TFA method to backup incase user gets logged out, so they're not locked out of their account - let tfaSendMethod = 'none' - if (user.emailValidated && user.tfaSendMethod !== 'none') { - tfaSendMethod = 'email' - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { - phoneDetails: null, - phoneValidated: false, - tfaSendMethod: ${tfaSendMethod} - } - UPDATE { - phoneDetails: null, - phoneValidated: false, - tfaSendMethod: ${tfaSendMethod} - } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred well removing phone number for user: ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove phone number. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred well removing phone number for user: ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to remove phone number. Please try again.`), - ) - } - - console.info(`User: ${user._key} successfully removed their phone number.`) - return { - _type: 'result', - status: i18n._(t`Phone number has been successfully removed.`), - } - }, -}) diff --git a/api-js/src/user/mutations/reset-password.js b/api-js/src/user/mutations/reset-password.js deleted file mode 100644 index 335c7feaaa..0000000000 --- a/api-js/src/user/mutations/reset-password.js +++ /dev/null @@ -1,171 +0,0 @@ -import { GraphQLNonNull, GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { resetPasswordUnion } from '../unions' - -export const resetPassword = new mutationWithClientMutationId({ - name: 'ResetPassword', - description: - 'This mutation allows the user to take the token they received in their email to reset their password.', - inputFields: () => ({ - password: { - type: GraphQLNonNull(GraphQLString), - description: 'The users new password.', - }, - confirmPassword: { - type: GraphQLNonNull(GraphQLString), - description: 'A confirmation password to confirm the new password.', - }, - resetToken: { - type: GraphQLNonNull(GraphQLString), - description: - 'The JWT found in the url, redirected from the email they received.', - }, - }), - outputFields: () => ({ - result: { - type: resetPasswordUnion, - description: - '`ResetPasswordUnion` returning either a `ResetPasswordResult`, or `ResetPasswordError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { verifyToken, bcrypt }, - loaders: { loadUserByKey }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse input - const password = cleanseInput(args.password) - const confirmPassword = cleanseInput(args.confirmPassword) - const resetToken = cleanseInput(args.resetToken) - - // Check if reset token is valid - const tokenParameters = verifyToken({ token: resetToken }) - - // Check to see if user id exists in token params !!! - if ( - tokenParameters.userKey === 'undefined' || - typeof tokenParameters.userKey === 'undefined' - ) { - console.warn( - `When resetting password user attempted to verify account, but userKey is not located in the token parameters.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Incorrect token value. Please request a new email.`, - ), - } - } - - // Check if user exists - const user = await loadUserByKey.load(tokenParameters.userKey) - - // Replace with userRequired() - if (typeof user === 'undefined') { - console.warn( - `A user attempted to reset the password for ${tokenParameters.userKey}, however there is no associated account.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to reset password. Please try again.`), - } - } - - // Check if password in token matches token in db - if (tokenParameters.currentPassword !== user.password) { - console.warn( - `User: ${user._key} attempted to reset password, however the current password does not match the current hashed password in the db.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to reset password. Please request a new email.`, - ), - } - } - - // Check to see if newly submitted passwords match - if (password !== confirmPassword) { - console.warn( - `User: ${user._key} attempted to reset their password, however the submitted passwords do not match.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`New passwords do not match.`), - } - } - - // Check to see if password meets GoC requirements - if (password.length < 12) { - console.warn( - `User: ${user._key} attempted to reset their password, however the submitted password is not long enough.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Password does not meet requirements.`), - } - } - - // Update users password in db - const hashedPassword = bcrypt.hashSync(password, 10) - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - try { - await trx.step( - () => query` - WITH users - FOR user IN users - UPDATE ${user._key} - WITH { - password: ${hashedPassword}, - failedLoginAttempts: 0 - } IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when user: ${user._key} attempted to reset their password: ${err}`, - ) - throw new Error(i18n._(t`Unable to reset password. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${user._key} attempted to authenticate: ${err}`, - ) - throw new Error(i18n._(t`Unable to reset password. Please try again.`)) - } - - console.info(`User: ${user._key} successfully reset their password.`) - - return { - _type: 'regular', - status: i18n._(t`Password was successfully reset.`), - } - }, -}) diff --git a/api-js/src/user/mutations/send-email-verification.js b/api-js/src/user/mutations/send-email-verification.js deleted file mode 100644 index db60c2b5f4..0000000000 --- a/api-js/src/user/mutations/send-email-verification.js +++ /dev/null @@ -1,65 +0,0 @@ -import { GraphQLString, GraphQLNonNull } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' -import { t } from '@lingui/macro' - -export const sendEmailVerification = new mutationWithClientMutationId({ - name: 'SendEmailVerification', - description: - 'This mutation is used for re-sending a verification email if it failed during user creation.', - inputFields: () => ({ - userName: { - type: GraphQLNonNull(GraphQLEmailAddress), - description: - 'The users email address used for sending the verification email.', - }, - }), - outputFields: () => ({ - status: { - type: GraphQLString, - description: 'Informs the user if the email was sent successfully.', - resolve: async (payload) => { - return payload.status - }, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - request, - auth: { tokenize }, - validators: { cleanseInput }, - loaders: { loadUserByUserName }, - notify: { sendVerificationEmail }, - }, - ) => { - // Cleanse Input - const userName = cleanseInput(args.userName).toLowerCase() - - // Get user from db - const user = await loadUserByUserName.load(userName) - - if (typeof user !== 'undefined') { - const token = tokenize({ - parameters: { userKey: user._key }, - }) - - const verifyUrl = `https://${request.get('host')}/validate/${token}` - - await sendVerificationEmail({ user, verifyUrl }) - - console.info(`User: ${user._key} successfully sent a verification email.`) - } else { - console.warn( - `A user attempted to send a verification email for ${userName} but no account is affiliated with this user name.`, - ) - } - - return { - status: i18n._( - t`If an account with this username is found, an email verification link will be found in your inbox.`, - ), - } - }, -}) diff --git a/api-js/src/user/mutations/send-password-reset.js b/api-js/src/user/mutations/send-password-reset.js deleted file mode 100644 index b55f342e9a..0000000000 --- a/api-js/src/user/mutations/send-password-reset.js +++ /dev/null @@ -1,66 +0,0 @@ -import { GraphQLNonNull, GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' -import { t } from '@lingui/macro' - -export const sendPasswordResetLink = new mutationWithClientMutationId({ - name: 'SendPasswordResetLink', - description: - 'This mutation allows a user to provide their username and request that a password reset email be sent to their account with a reset token in a url.', - inputFields: () => ({ - userName: { - type: GraphQLNonNull(GraphQLEmailAddress), - description: - 'User name for the account you would like to receive a password reset link for.', - }, - }), - outputFields: () => ({ - status: { - type: GraphQLString, - description: - 'Informs the user if the password reset email was sent successfully.', - resolve: async (payload) => { - return payload.status - }, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - request, - auth: { tokenize }, - validators: { cleanseInput }, - loaders: { loadUserByUserName }, - notify: { sendPasswordResetEmail }, - }, - ) => { - // Cleanse Input - const userName = cleanseInput(args.userName).toLowerCase() - - const user = await loadUserByUserName.load(userName) - - if (typeof user !== 'undefined') { - const token = tokenize({ - parameters: { userKey: user._key, currentPassword: user.password }, - }) - const resetUrl = `https://${request.get('host')}/reset-password/${token}` - - await sendPasswordResetEmail({ user, resetUrl }) - - console.info( - `User: ${user._key} successfully sent a password reset email.`, - ) - } else { - console.warn( - `A user attempted to send a password reset email for ${userName} but no account is affiliated with this user name.`, - ) - } - - return { - status: i18n._( - t`If an account with this username is found, a password reset link will be found in your inbox.`, - ), - } - }, -}) diff --git a/api-js/src/user/mutations/set-phone-number.js b/api-js/src/user/mutations/set-phone-number.js deleted file mode 100644 index 6dd99540b9..0000000000 --- a/api-js/src/user/mutations/set-phone-number.js +++ /dev/null @@ -1,135 +0,0 @@ -import crypto from 'crypto' -import { GraphQLNonNull } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { GraphQLPhoneNumber } from 'graphql-scalars' -import { t } from '@lingui/macro' - -import { setPhoneNumberUnion } from '../unions' - -const { CIPHER_KEY } = process.env - -export const setPhoneNumber = new mutationWithClientMutationId({ - name: 'SetPhoneNumber', - description: - 'This mutation is used for setting a new phone number for a user, and sending a code for verifying the new number.', - inputFields: () => ({ - phoneNumber: { - type: GraphQLNonNull(GraphQLPhoneNumber), - description: 'The phone number that the text message will be sent to.', - }, - }), - outputFields: () => ({ - result: { - type: setPhoneNumberUnion, - description: - '`SetPhoneNumberUnion` returning either a `SetPhoneNumberResult`, or `SetPhoneNumberError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { userRequired }, - loaders: { loadUserByKey }, - validators: { cleanseInput }, - notify: { sendTfaTextMsg }, - }, - ) => { - // Cleanse input - const phoneNumber = cleanseInput(args.phoneNumber) - - // Get User From Db - let user = await userRequired() - - // Generate TFA code - const tfaCode = Math.floor(100000 + Math.random() * 900000) - - // Generate Phone Details - const phoneDetails = { - iv: crypto.randomBytes(12).toString('hex'), - } - - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(phoneDetails.iv, 'hex'), - { authTagLength: 16 }, - ) - let encrypted = cipher.update(phoneNumber, 'utf8', 'hex') - encrypted += cipher.final('hex') - - phoneDetails.phoneNumber = encrypted - phoneDetails.tag = cipher.getAuthTag().toString('hex') - - // Set TFA method to backup incase user gets logged out, so they're not locked out of their account - let tfaSendMethod = 'none' - if (user.emailValidated && user.tfaSendMethod !== 'none') { - tfaSendMethod = 'email' - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Insert TFA code into DB - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { - tfaCode: ${tfaCode}, - phoneDetails: ${phoneDetails}, - phoneValidated: false, - tfaSendMethod: ${tfaSendMethod} - } - UPDATE { - tfaCode: ${tfaCode}, - phoneDetails: ${phoneDetails}, - phoneValidated: false, - tfaSendMethod: ${tfaSendMethod} - } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred for user: ${user._key} when upserting phone number information: ${err}`, - ) - throw new Error(i18n._(t`Unable to set phone number, please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred for user: ${user._key} when upserting phone number information: ${err}`, - ) - throw new Error(i18n._(t`Unable to set phone number, please try again.`)) - } - - // Get newly updated user - await loadUserByKey.clear(user._key) - user = await loadUserByKey.load(user._key) - - await sendTfaTextMsg({ phoneNumber, user }) - - console.info(`User: ${user._key} successfully set phone number.`) - return { - _type: 'regular', - user: user, - status: i18n._( - t`Phone number has been successfully set, you will receive a verification text message shortly.`, - ), - } - }, -}) diff --git a/api-js/src/user/mutations/sign-in.js b/api-js/src/user/mutations/sign-in.js deleted file mode 100644 index 0408b78f30..0000000000 --- a/api-js/src/user/mutations/sign-in.js +++ /dev/null @@ -1,300 +0,0 @@ -import { GraphQLBoolean, GraphQLNonNull, GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' -import { t } from '@lingui/macro' - -import { signInUnion } from '../../user' - -const { SIGN_IN_KEY, REFRESH_TOKEN_EXPIRY, REFRESH_KEY } = process.env - -export const signIn = new mutationWithClientMutationId({ - name: 'SignIn', - description: - 'This mutation allows users to give their credentials and either signed in, re-directed to the tfa auth page, or given an error.', - inputFields: () => ({ - userName: { - type: GraphQLNonNull(GraphQLEmailAddress), - description: 'The email the user signed up with.', - }, - password: { - type: GraphQLNonNull(GraphQLString), - description: 'The password the user signed up with', - }, - rememberMe: { - type: GraphQLBoolean, - defaultValue: false, - description: - 'Whether or not the user wants to stay signed in after leaving the site.', - }, - }), - outputFields: () => ({ - result: { - type: signInUnion, - description: - '`SignInUnion` returning either a `regularSignInResult`, `tfaSignInResult`, or `signInError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - uuidv4, - response, - auth: { tokenize, bcrypt }, - loaders: { loadUserByUserName }, - validators: { cleanseInput }, - notify: { sendAuthEmail, sendAuthTextMsg }, - }, - ) => { - // Cleanse input - const userName = cleanseInput(args.userName).toLowerCase() - const password = cleanseInput(args.password) - const rememberMe = args.rememberMe - - // Gather user who just signed in - let user = await loadUserByUserName.load(userName) - - // Replace with userRequired() - if (typeof user === 'undefined') { - console.warn( - `User: ${userName} attempted to sign in, no account is associated with this email.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Incorrect username or password. Please try again.`, - ), - } - } - - // Check against failed attempt info - if (user.failedLoginAttempts >= 10) { - console.warn( - `User: ${user._key} tried to sign in, but has too many login attempts.`, - ) - return { - _type: 'error', - code: 401, - description: i18n._( - t`Too many failed login attempts, please reset your password, and try again.`, - ), - } - } else { - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Check to see if passwords match - if (bcrypt.compareSync(password, user.password)) { - // Reset Failed Login attempts - try { - await trx.step( - () => query` - WITH users - FOR u IN users - UPDATE ${user._key} WITH { failedLoginAttempts: 0 } IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when resetting failed login attempts for user: ${user._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - const refreshId = uuidv4() - const refreshInfo = { - refreshId, - expiresAt: new Date( - new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), - rememberMe, - } - - if (user.tfaSendMethod !== 'none') { - // Generate TFA code - const tfaCode = Math.floor(100000 + Math.random() * 900000) - - // Insert TFA code into DB - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { - tfaCode: ${tfaCode}, - refreshInfo: ${refreshInfo} - } - UPDATE { - tfaCode: ${tfaCode}, - refreshInfo: ${refreshInfo} - } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when inserting TFA code for user: ${user._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${user._key} attempted to tfa sign in: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - // Get newly updated user - await loadUserByUserName.clear(userName) - user = await loadUserByUserName.load(userName) - - // Check to see if user has phone validated - let sendMethod - if (user.tfaSendMethod === 'phone') { - await sendAuthTextMsg({ user }) - sendMethod = 'text' - } else { - await sendAuthEmail({ user }) - sendMethod = 'email' - } - - console.info( - `User: ${user._key} successfully signed in, and sent auth msg.`, - ) - - const authenticateToken = tokenize({ - parameters: { userKey: user._key }, - secret: String(SIGN_IN_KEY), - }) - - return { - _type: 'tfa', - sendMethod, - authenticateToken, - } - } else { - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { refreshInfo: ${refreshInfo} } - UPDATE { refreshInfo: ${refreshInfo} } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when attempting to setting refresh tokens for user: ${user._key} during sign in: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${user._key} attempted a regular sign in: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - const token = tokenize({ - parameters: { userKey: user._key }, - }) - - const refreshToken = tokenize({ - parameters: { userKey: user._key, uuid: refreshId }, - expPeriod: 168, - secret: String(REFRESH_KEY), - }) - - // if the user does not want to stay logged in, create http session cookie - let cookieData = { - httpOnly: true, - secure: true, - sameSite: true, - expires: 0, - } - - // if user wants to stay logged in create normal http cookie - if (rememberMe) { - cookieData = { - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - httpOnly: true, - secure: true, - sameSite: true, - } - } - - response.cookie('refresh_token', refreshToken, cookieData) - - console.info( - `User: ${user._key} successfully signed in, and sent auth msg.`, - ) - - return { - _type: 'regular', - token, - user, - } - } - } else { - // increment failed login attempts - user.failedLoginAttempts += 1 - - try { - // Increase users failed login attempts - await trx.step( - () => query` - WITH users - FOR u IN users - UPDATE ${user._key} WITH { - failedLoginAttempts: ${user.failedLoginAttempts} - } IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when incrementing failed login attempts for user: ${user._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred while user: ${user._key} failed to sign in: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign in, please try again.`)) - } - - console.warn( - `User attempted to authenticate: ${user._key} with invalid credentials.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Incorrect username or password. Please try again.`, - ), - } - } - } - }, -}) diff --git a/api-js/src/user/mutations/sign-out.js b/api-js/src/user/mutations/sign-out.js deleted file mode 100644 index 965ccb2aaf..0000000000 --- a/api-js/src/user/mutations/sign-out.js +++ /dev/null @@ -1,28 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' - -export const signOut = new mutationWithClientMutationId({ - name: 'SignOut', - description: - 'This mutation allows a user to sign out, and clear their cookies.', - outputFields: () => ({ - status: { - type: GraphQLString, - description: 'Status of the users signing-out.', - resolve: ({ status }) => status, - }, - }), - mutateAndGetPayload: async (_, { i18n, response }) => { - response.cookie('refresh_token', '', { - httpOnly: true, - expires: new Date(0), - secure: true, - sameSite: true, - }) - - return { - status: i18n._(t`Successfully signed out.`), - } - }, -}) diff --git a/api-js/src/user/mutations/sign-up.js b/api-js/src/user/mutations/sign-up.js deleted file mode 100644 index c37f451f07..0000000000 --- a/api-js/src/user/mutations/sign-up.js +++ /dev/null @@ -1,296 +0,0 @@ -import { GraphQLBoolean, GraphQLNonNull, GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' -import { GraphQLEmailAddress } from 'graphql-scalars' - -import { LanguageEnums } from '../../enums' -import { signUpUnion } from '../unions' - -const { REFRESH_TOKEN_EXPIRY, REFRESH_KEY } = process.env - -export const signUp = new mutationWithClientMutationId({ - name: 'SignUp', - description: - 'This mutation allows for new users to sign up for our sites services.', - inputFields: () => ({ - displayName: { - type: GraphQLNonNull(GraphQLString), - description: 'The name that will be displayed to other users.', - }, - userName: { - type: GraphQLNonNull(GraphQLEmailAddress), - description: 'Email address that the user will use to authenticate with.', - }, - password: { - type: GraphQLNonNull(GraphQLString), - description: 'The password the user will authenticate with.', - }, - confirmPassword: { - type: GraphQLNonNull(GraphQLString), - description: - 'A secondary password field used to confirm the user entered the correct password.', - }, - preferredLang: { - type: GraphQLNonNull(LanguageEnums), - description: 'The users preferred language.', - }, - signUpToken: { - type: GraphQLString, - description: - 'A token sent by email, that will assign a user to an organization with a pre-determined role.', - }, - rememberMe: { - type: GraphQLBoolean, - defaultValue: false, - description: - 'Whether or not the user wants to stay signed in after leaving the site.', - }, - }), - outputFields: () => ({ - result: { - type: signUpUnion, - description: - '`SignUpUnion` returning either a `AuthResult`, or `SignUpError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - collections, - query, - transaction, - request, - response, - uuidv4, - auth: { bcrypt, tokenize, verifyToken }, - loaders: { loadOrgByKey, loadUserByUserName, loadUserByKey }, - notify: { sendVerificationEmail }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Inputs - const displayName = cleanseInput(args.displayName) - const userName = cleanseInput(args.userName).toLowerCase() - const password = cleanseInput(args.password) - const confirmPassword = cleanseInput(args.confirmPassword) - const preferredLang = cleanseInput(args.preferredLang) - const signUpToken = cleanseInput(args.signUpToken) - const rememberMe = args.rememberMe - - // Check to make sure password meets length requirement - if (password.length < 12) { - console.warn( - `User: ${userName} tried to sign up but did not meet requirements.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Password does not meet requirements.`), - } - } - - // Check that password and password confirmation match - if (password !== confirmPassword) { - console.warn( - `User: ${userName} tried to sign up but passwords do not match.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Passwords do not match.`), - } - } - - // Check to see if user already exists - const checkUser = await loadUserByUserName.load(userName) - - if (typeof checkUser !== 'undefined') { - console.warn( - `User: ${userName} tried to sign up, however there is already an account in use with that email.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Email already in use.`), - } - } - - // Hash Users Password - const hashedPassword = bcrypt.hashSync(password, 10) - - const refreshId = uuidv4() - - // Create User Structure for insert - const user = { - displayName: displayName, - userName: userName, - password: hashedPassword, - preferredLang: preferredLang, - phoneValidated: false, - emailValidated: false, - failedLoginAttempts: 0, - tfaSendMethod: 'none', - refreshInfo: { - refreshId, - rememberMe, - expiresAt: new Date( - new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), - }, - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - let insertedUserCursor - try { - insertedUserCursor = await trx.step( - () => query` - WITH users - INSERT ${user} INTO users - RETURN MERGE( - { - id: NEW._key, - _type: "user" - }, - NEW - ) - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred while user: ${userName} attempted to sign up, creating user: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign up. Please try again.`)) - } - - let insertedUser - try { - insertedUser = await insertedUserCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userName} attempted to sign up, creating user: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign up. Please try again.`)) - } - - // Assign user to org - if (signUpToken !== '') { - // Gather token parameters - const tokenParameters = verifyToken({ - token: signUpToken, - }) - - const tokenUserName = cleanseInput(tokenParameters.userName) - const tokenOrgKey = cleanseInput(tokenParameters.orgKey) - const tokenRequestedRole = cleanseInput(tokenParameters.requestedRole) - - if (userName !== tokenUserName) { - console.warn( - `User: ${userName} attempted to sign up with an invite token, however emails do not match.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to sign up, please contact org admin for a new invite.`, - ), - } - } - - const checkOrg = await loadOrgByKey.load(tokenOrgKey) - if (typeof checkOrg === 'undefined') { - console.warn( - `User: ${userName} attempted to sign up with an invite token, however the org could not be found.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to sign up, please contact org admin for a new invite.`, - ), - } - } - - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - INSERT { - _from: ${checkOrg._id}, - _to: ${insertedUser._id}, - permission: ${tokenRequestedRole}, - owner: false - } INTO affiliations - `, - ) - } catch (err) { - console.error( - `Transaction step error occurred while user: ${userName} attempted to sign up, assigning affiliation: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign up. Please try again.`)) - } - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction commit error occurred while user: ${userName} attempted to sign up: ${err}`, - ) - throw new Error(i18n._(t`Unable to sign up. Please try again.`)) - } - - const returnUser = await loadUserByKey.load(insertedUser._key) - - // Generate JWTs - const token = tokenize({ parameters: { userKey: insertedUser._key } }) - - const verifyUrl = `https://${request.get('host')}/validate/${token}` - - await sendVerificationEmail({ user: returnUser, verifyUrl }) - - const refreshToken = tokenize({ - parameters: { userKey: user._key, uuid: refreshId }, - expPeriod: 168, - secret: String(REFRESH_KEY), - }) - - // if the user does not want to stay logged in, create http session cookie - let cookieData = { - httpOnly: true, - secure: true, - sameSite: true, - expires: 0, - } - - // if user wants to stay logged in create normal http cookie - if (rememberMe) { - cookieData = { - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - httpOnly: true, - secure: true, - sameSite: true, - } - } - - response.cookie('refresh_token', refreshToken, cookieData) - - console.info(`User: ${userName} successfully created a new account.`) - - return { - _type: 'authResult', - token, - user: returnUser, - } - }, -}) diff --git a/api-js/src/user/mutations/update-user-password.js b/api-js/src/user/mutations/update-user-password.js deleted file mode 100644 index 0aa40c364b..0000000000 --- a/api-js/src/user/mutations/update-user-password.js +++ /dev/null @@ -1,137 +0,0 @@ -import { GraphQLString, GraphQLNonNull } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { updateUserPasswordUnion } from '../unions' - -export const updateUserPassword = new mutationWithClientMutationId({ - name: 'UpdateUserPassword', - description: - 'This mutation allows the user to update their account password.', - inputFields: () => ({ - currentPassword: { - type: GraphQLNonNull(GraphQLString), - description: - 'The users current password to verify it is the current user.', - }, - updatedPassword: { - type: GraphQLNonNull(GraphQLString), - description: 'The new password the user wishes to change to.', - }, - updatedPasswordConfirm: { - type: GraphQLNonNull(GraphQLString), - description: 'A password confirmation of their new password.', - }, - }), - outputFields: () => ({ - result: { - type: updateUserPasswordUnion, - description: - '`UpdateUserPasswordUnion` returning either a `UpdateUserPasswordResultType`, or `UpdateUserPasswordError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { bcrypt, userRequired }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Input - const currentPassword = cleanseInput(args.currentPassword) - const updatedPassword = cleanseInput(args.updatedPassword) - const updatedPasswordConfirm = cleanseInput(args.updatedPasswordConfirm) - - // Get user from db - const user = await userRequired() - - // Check to see if current passwords match - if (!bcrypt.compareSync(currentPassword, user.password)) { - console.warn( - `User: ${user._key} attempted to update their password, however they did not enter the current password correctly.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to update password, current password does not match. Please try again.`, - ), - } - } - - // Check to see if new passwords match - if (updatedPassword !== updatedPasswordConfirm) { - console.warn( - `User: ${user._key} attempted to update their password, however the new passwords do not match.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to update password, new passwords do not match. Please try again.`, - ), - } - } - - // Check to see if they meet GoC requirements - if (updatedPassword.length < 12) { - console.warn( - `User: ${user._key} attempted to update their password, however the new password does not meet GoC requirements.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to update password, passwords do not match requirements. Please try again.`, - ), - } - } - - // Update password in DB - const hashedPassword = bcrypt.hashSync(updatedPassword, 10) - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - try { - await trx.step( - () => query` - WITH users - FOR user IN users - UPDATE ${user._key} WITH { password: ${hashedPassword} } IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when user: ${user._key} attempted to update their password: ${err}`, - ) - throw new Error(i18n._(t`Unable to update password. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when user: ${user._key} attempted to update their password: ${err}`, - ) - throw new Error(i18n._(t`Unable to update password. Please try again.`)) - } - - console.info(`User: ${user._key} successfully updated their password.`) - return { - _type: 'regular', - status: i18n._(t`Password was successfully updated.`), - } - }, -}) diff --git a/api-js/src/user/mutations/update-user-profile.js b/api-js/src/user/mutations/update-user-profile.js deleted file mode 100644 index d9ec092f89..0000000000 --- a/api-js/src/user/mutations/update-user-profile.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' -import { t } from '@lingui/macro' - -import { LanguageEnums, TfaSendMethodEnum } from '../../enums' -import { updateUserProfileUnion } from '../unions' - -export const updateUserProfile = new mutationWithClientMutationId({ - name: 'UpdateUserProfile', - description: - 'This mutation allows the user to update their user profile to change various details of their current profile.', - inputFields: () => ({ - displayName: { - type: GraphQLString, - description: 'The updated display name the user wishes to change to.', - }, - userName: { - type: GraphQLEmailAddress, - description: 'The updated user name the user wishes to change to.', - }, - preferredLang: { - type: LanguageEnums, - description: - 'The updated preferred language the user wishes to change to.', - }, - tfaSendMethod: { - type: TfaSendMethodEnum, - description: - 'The method in which the user wishes to have their TFA code sent via.', - }, - }), - outputFields: () => ({ - result: { - type: updateUserProfileUnion, - description: - '`UpdateUserProfileUnion` returning either a `UpdateUserProfileResult`, or `UpdateUserProfileError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - userKey, - request, - auth: { tokenize, userRequired }, - loaders: { loadUserByKey, loadUserByUserName }, - notify: { sendVerificationEmail }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Input - const displayName = cleanseInput(args.displayName) - const userName = cleanseInput(args.userName).toLowerCase() - const preferredLang = cleanseInput(args.preferredLang) - const subTfaSendMethod = cleanseInput(args.tfaSendMethod) - - // Get user info from DB - const user = await userRequired() - - // Check to see if user name is already in use - if (userName !== '') { - const checkUser = await loadUserByUserName.load(userName) - if (typeof checkUser !== 'undefined') { - console.warn( - `User: ${userKey} attempted to update their username, but the username is already in use.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Username not available, please try another.`), - } - } - } - - let tfaSendMethod - if (subTfaSendMethod === 'phone' && user.phoneValidated) { - tfaSendMethod = 'phone' - } else if (subTfaSendMethod === 'email' && user.emailValidated) { - tfaSendMethod = 'email' - } else if ( - subTfaSendMethod === 'none' || - typeof user.tfaSendMethod === 'undefined' - ) { - tfaSendMethod = 'none' - } else { - tfaSendMethod = user.tfaSendMethod - } - - let emailValidated = user.emailValidated - let changedUserName = false - if (userName !== user.userName && userName !== '') { - changedUserName = true - emailValidated = false - } - - // Create object containing updated data - const updatedUser = { - displayName: displayName || user.displayName, - userName: userName || user.userName, - preferredLang: preferredLang || user.preferredLang, - tfaSendMethod: tfaSendMethod, - emailValidated, - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT ${updatedUser} - UPDATE ${updatedUser} - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when user: ${userKey} attempted to update their profile: ${err}`, - ) - throw new Error(i18n._(t`Unable to update profile. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when user: ${userKey} attempted to update their profile: ${err}`, - ) - throw new Error(i18n._(t`Unable to update profile. Please try again.`)) - } - - await loadUserByKey.clear(user._key) - const returnUser = await loadUserByKey.load(userKey) - - if (changedUserName) { - const token = tokenize({ parameters: { userKey: returnUser._key } }) - - const verifyUrl = `https://${request.get('host')}/validate/${token}` - - await sendVerificationEmail({ user: returnUser, verifyUrl }) - } - - console.info(`User: ${userKey} successfully updated their profile.`) - return { - _type: 'success', - status: i18n._(t`Profile successfully updated.`), - user: returnUser, - } - }, -}) diff --git a/api-js/src/user/mutations/verify-account.js b/api-js/src/user/mutations/verify-account.js deleted file mode 100644 index 6257813b08..0000000000 --- a/api-js/src/user/mutations/verify-account.js +++ /dev/null @@ -1,122 +0,0 @@ -import { GraphQLNonNull, GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { verifyAccountUnion } from '../unions' - -export const verifyAccount = new mutationWithClientMutationId({ - name: 'VerifyAccount', - description: - 'This mutation allows the user to verify their account through a token sent in an email.', - inputFields: () => ({ - verifyTokenString: { - type: GraphQLNonNull(GraphQLString), - description: 'Token sent via email, and located in url.', - }, - }), - outputFields: () => ({ - result: { - type: verifyAccountUnion, - description: - '`VerifyAccountUnion` returning either a `VerifyAccountResult`, or `VerifyAccountError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - query, - collections, - transaction, - auth: { verifyToken }, - loaders: { loadUserByKey }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse Input - const verifyTokenString = cleanseInput(args.verifyTokenString) - - // Get info from token - const tokenParameters = verifyToken({ token: verifyTokenString }) - - // Check to see if userKey exists in tokenParameters - if (!tokenParameters?.userKey) { - console.warn( - `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to verify account. Please request a new email.`, - ), - } - } - - // Auth shouldn't be needed with this - // Check if user exists - const { userKey } = tokenParameters - const user = await loadUserByKey.load(userKey) - - if (typeof user === 'undefined') { - console.warn( - `User: ${userKey} attempted to verify account, however no account is associated with this id.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to verify account. Please request a new email.`, - ), - } - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Verify users account - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { emailValidated: true } - UPDATE { emailValidated: true } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when upserting email validation for user: ${user._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable to verify account. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when upserting email validation for user: ${user._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable to verify account. Please try again.`)) - } - - console.info( - `User: ${user._key} successfully email validated their account.`, - ) - - return { - _type: 'success', - status: i18n._( - t`Successfully email verified account, and set TFA send method to email.`, - ), - } - }, -}) diff --git a/api-js/src/user/mutations/verify-phone-number.js b/api-js/src/user/mutations/verify-phone-number.js deleted file mode 100644 index 5fcf561ed8..0000000000 --- a/api-js/src/user/mutations/verify-phone-number.js +++ /dev/null @@ -1,122 +0,0 @@ -import { GraphQLNonNull, GraphQLInt } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' -import { t } from '@lingui/macro' - -import { verifyPhoneNumberUnion } from '../unions' - -export const verifyPhoneNumber = new mutationWithClientMutationId({ - name: 'verifyPhoneNumber', - description: 'This mutation allows the user to two factor authenticate.', - inputFields: () => ({ - twoFactorCode: { - type: GraphQLNonNull(GraphQLInt), - description: 'The two factor code that was received via text message.', - }, - }), - outputFields: () => ({ - result: { - type: verifyPhoneNumberUnion, - description: - '`VerifyPhoneNumberUnion` returning either a `VerifyPhoneNumberResult`, or `VerifyPhoneNumberError` object.', - resolve: (payload) => payload, - }, - }), - mutateAndGetPayload: async ( - args, - { - i18n, - userKey, - query, - collections, - transaction, - auth: { userRequired }, - loaders: { loadUserByKey }, - }, - ) => { - // Cleanse Input - const twoFactorCode = args.twoFactorCode - - // Get User From DB - const user = await userRequired() - - if (twoFactorCode.toString().length !== 6) { - console.warn( - `User: ${user._key} attempted to two factor authenticate, however the code they submitted does not have 6 digits.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Two factor code length is incorrect. Please try again.`, - ), - } - } - - // Check that TFA codes match - if (twoFactorCode !== user.tfaCode) { - console.warn( - `User: ${user._key} attempted to two factor authenticate, however the tfa codes do not match.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Two factor code is incorrect. Please try again.`), - } - } - - // Generate list of collections names - const collectionStrings = [] - for (const property in collections) { - collectionStrings.push(property.toString()) - } - - // Setup Transaction - const trx = await transaction(collectionStrings) - - // Update phoneValidated to be true - try { - await trx.step( - () => query` - WITH users - UPSERT { _key: ${user._key} } - INSERT { phoneValidated: true } - UPDATE { phoneValidated: true } - IN users - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when upserting the tfaValidate field for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to two factor authenticate. Please try again.`), - ) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when upserting the tfaValidate field for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to two factor authenticate. Please try again.`), - ) - } - - await loadUserByKey.clear(userKey) - const updatedUser = await loadUserByKey.load(userKey) - - console.info( - `User: ${user._key} successfully two factor authenticated their account.`, - ) - - return { - _type: 'success', - user: updatedUser, - status: i18n._( - t`Successfully verified phone number, and set TFA send method to text.`, - ), - } - }, -}) diff --git a/api-js/src/user/objects/__tests__/remove-phone-number-result.test.js b/api-js/src/user/objects/__tests__/remove-phone-number-result.test.js deleted file mode 100644 index 5428d501d5..0000000000 --- a/api-js/src/user/objects/__tests__/remove-phone-number-result.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { GraphQLString } from 'graphql' - -import { removePhoneNumberResultType } from '../remove-phone-number-result' - -describe('given the removePhoneNumberResultType object', () => { - describe('testing the field definitions', () => { - it('has an status field', () => { - const demoType = removePhoneNumberResultType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(GraphQLString) - }) - }) - - describe('testing the field resolvers', () => { - describe('testing the status resolver', () => { - it('returns the resolved field', () => { - const demoType = removePhoneNumberResultType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - }) -}) diff --git a/api-js/src/user/objects/__tests__/update-user-password-result.test.js b/api-js/src/user/objects/__tests__/update-user-password-result.test.js deleted file mode 100644 index b4688b7e53..0000000000 --- a/api-js/src/user/objects/__tests__/update-user-password-result.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { GraphQLString } from 'graphql' - -import { updateUserPasswordResultType } from '../update-user-password-result' - -describe('given the updateUserPasswordResultType object', () => { - describe('testing the field definitions', () => { - it('has an status field', () => { - const demoType = updateUserPasswordResultType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(GraphQLString) - }) - }) - - describe('testing the field resolvers', () => { - describe('testing the status resolver', () => { - it('returns the resolved field', () => { - const demoType = updateUserPasswordResultType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - }) -}) diff --git a/api-js/src/user/objects/__tests__/user-personal.test.js b/api-js/src/user/objects/__tests__/user-personal.test.js deleted file mode 100644 index 7b28f2112f..0000000000 --- a/api-js/src/user/objects/__tests__/user-personal.test.js +++ /dev/null @@ -1,259 +0,0 @@ -import crypto from 'crypto' -import { - GraphQLNonNull, - GraphQLID, - GraphQLString, - GraphQLBoolean, -} from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { GraphQLEmailAddress, GraphQLPhoneNumber } from 'graphql-scalars' - -import { affiliationConnection } from '../../../affiliation/objects' -import { userPersonalType } from '../index' -import { LanguageEnums, TfaSendMethodEnum } from '../../../enums' -import { decryptPhoneNumber } from '../../../validators' - -const { CIPHER_KEY } = process.env - -describe('given the user object', () => { - describe('testing the field definitions', () => { - it('has an id field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a userName field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('userName') - expect(demoType.userName.type).toMatchObject(GraphQLEmailAddress) - }) - it('has a displayName field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('displayName') - expect(demoType.displayName.type).toMatchObject(GraphQLString) - }) - it('has a phoneNumber field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('phoneNumber') - expect(demoType.phoneNumber.type).toMatchObject(GraphQLPhoneNumber) - }) - it('has a preferredLang field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('preferredLang') - expect(demoType.preferredLang.type).toMatchObject(LanguageEnums) - }) - it('has a phoneValidated field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('phoneValidated') - expect(demoType.phoneValidated.type).toMatchObject(GraphQLBoolean) - }) - it('has a emailValidated field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('emailValidated') - expect(demoType.emailValidated.type).toMatchObject(GraphQLBoolean) - }) - it('has a tfaSendMethod field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('tfaSendMethod') - expect(demoType.tfaSendMethod.type).toMatchObject(TfaSendMethodEnum) - }) - it('has an affiliations field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType).toHaveProperty('affiliations') - expect(demoType.affiliations.type).toMatchObject( - affiliationConnection.connectionType, - ) - }) - }) - describe('testing the field resolvers', () => { - describe('testing the id resolver', () => { - it('returns the resolved field', () => { - const demoType = userPersonalType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('user', '1'), - ) - }) - }) - describe('testing the userName field', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - expect( - demoType.userName.resolve({ userName: 'test@email.gc.ca' }), - ).toEqual('test@email.gc.ca') - }) - }) - describe('testing the displayName field', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - expect( - demoType.displayName.resolve({ displayName: 'display name' }), - ).toEqual('display name') - }) - }) - describe('testing the phoneNumber field', () => { - describe('testing undefined phoneDetails', () => { - it('returns null', () => { - const demoType = userPersonalType.getFields() - - const phoneDetails = undefined - - expect( - demoType.phoneNumber.resolve( - { - phoneDetails, - }, - {}, - { validators: { decryptPhoneNumber } }, - ), - ).toEqual(null) - }) - }) - describe('testing null phoneDetails', () => { - it('returns null', () => { - const demoType = userPersonalType.getFields() - - const phoneDetails = null - - expect( - demoType.phoneNumber.resolve( - { - phoneDetails, - }, - {}, - { validators: { decryptPhoneNumber } }, - ), - ).toEqual(null) - }) - }) - describe('testing defined phoneDetails', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - const phoneDetails = { - iv: crypto.randomBytes(12).toString('hex'), - phoneNumber: '12345678912', - } - - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(phoneDetails.iv, 'hex'), - { - authTagLength: 16, - }, - ) - let encrypted = cipher.update(phoneDetails.phoneNumber, 'utf8', 'hex') - encrypted += cipher.final('hex') - - expect( - demoType.phoneNumber.resolve( - { - phoneDetails: { - phoneNumber: encrypted, - iv: phoneDetails.iv, - tag: cipher.getAuthTag().toString('hex'), - }, - }, - {}, - { validators: { decryptPhoneNumber } }, - ), - ).toEqual(phoneDetails.phoneNumber) - }) - }) - }) - describe('testing the preferredLang field', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - expect( - demoType.preferredLang.resolve({ preferredLang: 'english' }), - ).toEqual('english') - }) - }) - describe('testing the phoneValidated field', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - expect( - demoType.phoneValidated.resolve({ phoneValidated: true }), - ).toEqual(true) - }) - }) - describe('testing the emailValidated field', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - expect( - demoType.emailValidated.resolve({ emailValidated: true }), - ).toEqual(true) - }) - }) - describe('testing the tfaSendMethod field', () => { - it('returns the resolved value', () => { - const demoType = userPersonalType.getFields() - - expect( - demoType.tfaSendMethod.resolve({ tfaSendMethod: 'phone' }), - ).toEqual('phone') - }) - }) - describe('testing the affiliations field', () => { - it('returns the resolved value', async () => { - const demoType = userPersonalType.getFields() - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('affiliation', '1'), - node: { - _from: 'organizations/1', - _id: 'affiliations/1', - _key: '1', - _rev: 'rev', - _to: 'users/1', - _type: 'affiliation', - id: '1', - orgKey: '1', - permission: 'user', - userKey: '1', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('affiliation', '1'), - endCursor: toGlobalId('affiliation', '1'), - }, - } - - await expect( - demoType.affiliations.resolve( - { _id: '1' }, - { first: 1 }, - { - loaders: { - loadAffiliationConnectionsByUserId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/user/objects/auth-result.js b/api-js/src/user/objects/auth-result.js deleted file mode 100644 index c5a2b8d68d..0000000000 --- a/api-js/src/user/objects/auth-result.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userPersonalType } from './user-personal' - -export const authResultType = new GraphQLObjectType({ - name: 'AuthResult', - description: `An object used to return information when users sign up or authenticate.`, - fields: () => ({ - authToken: { - type: GraphQLString, - description: `JWT used for accessing controlled content.`, - resolve: ({ token }) => token, - }, - user: { - type: userPersonalType, - description: `User that has just been created or signed in.`, - resolve: ({ user }) => user, - }, - }), -}) diff --git a/api-js/src/user/objects/close-account-error.js b/api-js/src/user/objects/close-account-error.js deleted file mode 100644 index a85bc4f7ad..0000000000 --- a/api-js/src/user/objects/close-account-error.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' - -export const closeAccountError = new GraphQLObjectType({ - name: 'CloseAccountError', - description: - 'This object is used to inform the user if any errors occurred while closing their account.', - fields: () => ({ - code: { - type: GraphQLInt, - description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, - }, - description: { - type: GraphQLString, - description: 'Description of the issue encountered.', - resolve: ({ description }) => description, - }, - }), -}) diff --git a/api-js/src/user/objects/close-account-result.js b/api-js/src/user/objects/close-account-result.js deleted file mode 100644 index 960f38b951..0000000000 --- a/api-js/src/user/objects/close-account-result.js +++ /dev/null @@ -1,14 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' - -export const closeAccountResult = new GraphQLObjectType({ - name: 'CloseAccountResult', - description: - 'This object is used to inform the user of the status of closing their account.', - fields: () => ({ - status: { - type: GraphQLString, - description: 'Status of closing the users account.', - resolve: ({ status }) => status, - }, - }), -}) diff --git a/api-js/src/user/objects/index.js b/api-js/src/user/objects/index.js deleted file mode 100644 index 6edc31b0c3..0000000000 --- a/api-js/src/user/objects/index.js +++ /dev/null @@ -1,23 +0,0 @@ -export * from './auth-result' -export * from './authenticate-error' -export * from './close-account-error' -export * from './close-account-result' -export * from './remove-phone-number-error' -export * from './remove-phone-number-result' -export * from './reset-password-error' -export * from './reset-password-result' -export * from './set-phone-number-error' -export * from './set-phone-number-result' -export * from './sign-in-error' -export * from './sign-up-error' -export * from './tfa-sign-in-result' -export * from './update-user-password-error' -export * from './update-user-password-result' -export * from './update-user-profile-error' -export * from './update-user-profile-result' -export * from './user-personal' -export * from './user-shared' -export * from './verify-phone-number-error' -export * from './verify-phone-number-result' -export * from './verify-account-error' -export * from './verify-account-result' diff --git a/api-js/src/user/objects/set-phone-number-result.js b/api-js/src/user/objects/set-phone-number-result.js deleted file mode 100644 index 9fc28bc6c1..0000000000 --- a/api-js/src/user/objects/set-phone-number-result.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userPersonalType } from './user-personal' - -export const setPhoneNumberResultType = new GraphQLObjectType({ - name: 'SetPhoneNumberResult', - description: - 'This object is used to inform the user that no errors were encountered while setting a new phone number.', - fields: () => ({ - status: { - type: GraphQLString, - description: - 'Informs the user if their phone code was successfully sent.', - resolve: ({ status }) => status, - }, - user: { - type: userPersonalType, - description: 'The user who set their phone number.', - resolve: ({ user }) => user, - }, - }), -}) diff --git a/api-js/src/user/objects/sign-in-error.js b/api-js/src/user/objects/sign-in-error.js deleted file mode 100644 index 4afb152e7b..0000000000 --- a/api-js/src/user/objects/sign-in-error.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' - -export const signInError = new GraphQLObjectType({ - name: 'SignInError', - description: - 'This object is used to inform the user if any errors occurred during sign in.', - fields: () => ({ - code: { - type: GraphQLInt, - description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, - }, - description: { - type: GraphQLString, - description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, - }, - }), -}) diff --git a/api-js/src/user/objects/sign-up-error.js b/api-js/src/user/objects/sign-up-error.js deleted file mode 100644 index e45637a675..0000000000 --- a/api-js/src/user/objects/sign-up-error.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' - -export const signUpError = new GraphQLObjectType({ - name: 'SignUpError', - description: - 'This object is used to inform the user if any errors occurred during sign up.', - fields: () => ({ - code: { - type: GraphQLInt, - description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, - }, - description: { - type: GraphQLString, - description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, - }, - }), -}) diff --git a/api-js/src/user/objects/tfa-sign-in-result.js b/api-js/src/user/objects/tfa-sign-in-result.js deleted file mode 100644 index e880d8b55f..0000000000 --- a/api-js/src/user/objects/tfa-sign-in-result.js +++ /dev/null @@ -1,20 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' - -export const tfaSignInResult = new GraphQLObjectType({ - name: 'TFASignInResult', - description: - 'This object is used when the user signs in and has validated either their email or phone.', - fields: () => ({ - authenticateToken: { - type: GraphQLString, - description: 'Token used to verify during authentication.', - resolve: ({ authenticateToken }) => authenticateToken, - }, - sendMethod: { - type: GraphQLString, - description: - 'Whether the authentication code was sent through text, or email.', - resolve: ({ sendMethod }) => sendMethod, - }, - }), -}) diff --git a/api-js/src/user/objects/update-user-profile-result.js b/api-js/src/user/objects/update-user-profile-result.js deleted file mode 100644 index 27df736e00..0000000000 --- a/api-js/src/user/objects/update-user-profile-result.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userPersonalType } from './user-personal' - -export const updateUserProfileResultType = new GraphQLObjectType({ - name: 'UpdateUserProfileResult', - description: - 'This object is used to inform the user that no errors were encountered while resetting their password.', - fields: () => ({ - status: { - type: GraphQLString, - description: - 'Informs the user if the password reset was successful, and to redirect to sign in page.', - resolve: ({ status }) => status, - }, - user: { - type: userPersonalType, - description: 'Return the newly updated user information.', - resolve: ({ user }) => user, - }, - }), -}) diff --git a/api-js/src/user/objects/user-personal.js b/api-js/src/user/objects/user-personal.js deleted file mode 100644 index b2ecd376a1..0000000000 --- a/api-js/src/user/objects/user-personal.js +++ /dev/null @@ -1,88 +0,0 @@ -import { GraphQLBoolean, GraphQLObjectType, GraphQLString } from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLEmailAddress, GraphQLPhoneNumber } from 'graphql-scalars' - -import { affiliationOrgOrder } from '../../affiliation/inputs' -import { affiliationConnection } from '../../affiliation/objects' -import { LanguageEnums, TfaSendMethodEnum } from '../../enums' -import { nodeInterface } from '../../node' - -export const userPersonalType = new GraphQLObjectType({ - name: 'PersonalUser', - fields: () => ({ - id: globalIdField('user'), - userName: { - type: GraphQLEmailAddress, - description: 'Users email address.', - resolve: ({ userName }) => userName, - }, - displayName: { - type: GraphQLString, - description: 'Name displayed to other users.', - resolve: ({ displayName }) => displayName, - }, - phoneNumber: { - type: GraphQLPhoneNumber, - description: 'The phone number the user has setup with tfa.', - resolve: ( - { phoneDetails }, - _args, - { validators: { decryptPhoneNumber } }, - ) => { - if (typeof phoneDetails === 'undefined' || phoneDetails === null) { - return null - } - return decryptPhoneNumber(phoneDetails) - }, - }, - preferredLang: { - type: LanguageEnums, - description: 'Users preferred language.', - resolve: ({ preferredLang }) => preferredLang, - }, - phoneValidated: { - type: GraphQLBoolean, - description: 'Has the user completed phone validation.', - resolve: ({ phoneValidated }) => phoneValidated, - }, - emailValidated: { - type: GraphQLBoolean, - description: 'Has the user email verified their account.', - resolve: ({ emailValidated }) => emailValidated, - }, - tfaSendMethod: { - type: TfaSendMethodEnum, - description: 'The method in which TFA codes are sent.', - resolve: ({ tfaSendMethod }) => tfaSendMethod, - }, - affiliations: { - type: affiliationConnection.connectionType, - description: 'Users affiliations to various organizations.', - args: { - orderBy: { - type: affiliationOrgOrder, - description: 'Ordering options for affiliation connections.', - }, - search: { - type: GraphQLString, - description: 'String used to search for affiliated organizations.', - }, - ...connectionArgs, - }, - resolve: async ( - { _id }, - args, - { loaders: { loadAffiliationConnectionsByUserId } }, - ) => { - const affiliations = await loadAffiliationConnectionsByUserId({ - userId: _id, - ...args, - }) - return affiliations - }, - }, - }), - interfaces: [nodeInterface], - description: `This object is used for showing personal user details, -and is used for only showing the details of the querying user.`, -}) diff --git a/api-js/src/user/objects/user-shared.js b/api-js/src/user/objects/user-shared.js deleted file mode 100644 index 99e0330dda..0000000000 --- a/api-js/src/user/objects/user-shared.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { globalIdField } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' - -import { nodeInterface } from '../../node' - -export const userSharedType = new GraphQLObjectType({ - name: 'SharedUser', - fields: () => ({ - id: globalIdField('user'), - displayName: { - type: GraphQLString, - description: 'Users display name.', - resolve: ({ displayName }) => displayName, - }, - userName: { - type: GraphQLEmailAddress, - description: 'Users email address.', - resolve: ({ userName }) => userName, - }, - }), - interfaces: [nodeInterface], - description: `This object is used for showing none personal user details, -and is used for limiting admins to the personal details of users.`, -}) diff --git a/api-js/src/user/objects/verify-phone-number-result.js b/api-js/src/user/objects/verify-phone-number-result.js deleted file mode 100644 index c6c01c5827..0000000000 --- a/api-js/src/user/objects/verify-phone-number-result.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' - -import { userPersonalType } from './user-personal' - -export const verifyPhoneNumberResultType = new GraphQLObjectType({ - name: 'VerifyPhoneNumberResult', - description: - 'This object is used to inform the user that no errors were encountered while verifying their phone number.', - fields: () => ({ - status: { - type: GraphQLString, - description: - 'Informs the user if their phone number was successfully verified.', - resolve: ({ status }) => status, - }, - user: { - type: userPersonalType, - description: 'The user who verified their phone number.', - resolve: ({ user }) => user, - }, - }), -}) diff --git a/api-js/src/user/queries/find-me.js b/api-js/src/user/queries/find-me.js deleted file mode 100644 index a53b4881bc..0000000000 --- a/api-js/src/user/queries/find-me.js +++ /dev/null @@ -1,14 +0,0 @@ -import { userPersonalType } from '../objects' - -export const findMe = { - type: userPersonalType, - description: 'Query the currently logged in user.', - resolve: async (_, __, { auth: { userRequired } }) => { - // Get querying user - const user = await userRequired() - - user.id = user._key - - return user - }, -} diff --git a/api-js/src/user/queries/index.js b/api-js/src/user/queries/index.js deleted file mode 100644 index 1dac1282d4..0000000000 --- a/api-js/src/user/queries/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './find-me' -export * from './find-user-by-username' -export * from './is-user-admin' -export * from './is-user-super-admin' diff --git a/api-js/src/user/queries/is-user-admin.js b/api-js/src/user/queries/is-user-admin.js deleted file mode 100644 index ea0fbae271..0000000000 --- a/api-js/src/user/queries/is-user-admin.js +++ /dev/null @@ -1,68 +0,0 @@ -import { t } from '@lingui/macro' -import { GraphQLBoolean, GraphQLID } from 'graphql' -import { fromGlobalId } from 'graphql-relay' - -export const isUserAdmin = { - type: GraphQLBoolean, - description: 'Query used to check if the user has an admin role.', - args: { - orgId: { - type: GraphQLID, - description: - 'Optional org id to see if user is an admin for the requested org.', - }, - }, - resolve: async ( - _, - args, - { - i18n, - query, - userKey, - auth: { checkPermission, userRequired }, - loaders: { loadOrgByKey }, - validators: { cleanseInput }, - }, - ) => { - const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) - - const user = await userRequired() - - // check if for a specific org - if (orgKey !== '') { - const org = await loadOrgByKey.load(orgKey) - - const permission = await checkPermission({ orgId: org._id }) - - if (permission === 'admin' || permission === 'super_admin') { - return true - } - - return false - } - - // check to see if user is an admin or higher for at least one org - let userAdmin - try { - userAdmin = await query` - FOR v, e IN 1..1 INBOUND ${user._id} affiliations - FILTER e.permission == "admin" || e.permission == "super_admin" - LIMIT 1 - RETURN e.permission - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} was seeing if they were an admin, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify if user is an admin, please try again.`), - ) - } - - if (userAdmin.count > 0) { - return true - } - - return false - }, -} diff --git a/api-js/src/user/queries/is-user-super-admin.js b/api-js/src/user/queries/is-user-super-admin.js deleted file mode 100644 index e8a26dc78f..0000000000 --- a/api-js/src/user/queries/is-user-super-admin.js +++ /dev/null @@ -1,33 +0,0 @@ -import { GraphQLBoolean } from 'graphql' -import { t } from '@lingui/macro' - -export const isUserSuperAdmin = { - type: GraphQLBoolean, - description: 'Query used to check if the user has a super admin role.', - resolve: async (_, __, { i18n, query, userKey, auth: { userRequired } }) => { - const user = await userRequired() - - let userAdmin - try { - userAdmin = await query` - FOR v, e IN 1..1 INBOUND ${user._id} affiliations - FILTER e.permission == "super_admin" - LIMIT 1 - RETURN e.permission - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} was seeing if they were a super admin, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify if user is a super admin, please try again.`), - ) - } - - if (userAdmin.count > 0) { - return true - } - - return false - }, -} diff --git a/api-js/src/user/unions/__tests__/sign-up-union.test.js b/api-js/src/user/unions/__tests__/sign-up-union.test.js deleted file mode 100644 index 20c1e887b8..0000000000 --- a/api-js/src/user/unions/__tests__/sign-up-union.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { authResultType, signUpError } from '../../objects/index' -import { signUpUnion } from '../sign-up-union' - -describe('given the sign up union', () => { - describe('testing the field types', () => { - it('contains authResultType type', () => { - const demoType = signUpUnion.getTypes() - - expect(demoType).toContain(authResultType) - }) - it('contains signUpError type', () => { - const demoType = signUpUnion.getTypes() - - expect(demoType).toContain(signUpError) - }) - }) - describe('testing the field selection', () => { - describe('testing the authResult type', () => { - it('returns the correct type', () => { - const obj = { - _type: 'authResult', - authResult: {}, - } - - expect(signUpUnion.resolveType(obj)).toMatchObject(authResultType) - }) - }) - describe('testing the signUpError type', () => { - it('returns the correct type', () => { - const obj = { - _type: 'error', - error: 'sign-in-error', - code: 401, - description: 'text', - } - - expect(signUpUnion.resolveType(obj)).toMatchObject(signUpError) - }) - }) - }) -}) diff --git a/api-js/src/user/unions/close-account-union.js b/api-js/src/user/unions/close-account-union.js deleted file mode 100644 index 16aecaf7ab..0000000000 --- a/api-js/src/user/unions/close-account-union.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLUnionType } from 'graphql' -import { closeAccountError, closeAccountResult } from '../objects' - -export const closeAccountUnion = new GraphQLUnionType({ - name: 'CloseAccountUnion', - description: - 'This union is used for the `closeAccount` mutation, to support successful or errors that may occur.', - types: [closeAccountResult, closeAccountError], - resolveType({ _type }) { - if (_type === 'error') { - return closeAccountError - } else { - return closeAccountResult - } - }, -}) diff --git a/api-js/src/user/unions/index.js b/api-js/src/user/unions/index.js deleted file mode 100644 index 1a94618ed7..0000000000 --- a/api-js/src/user/unions/index.js +++ /dev/null @@ -1,12 +0,0 @@ -export * from './authenticate-union' -export * from './close-account-union' -export * from './refresh-tokens-union' -export * from './remove-phone-number-union' -export * from './reset-password-union' -export * from './set-phone-number-union' -export * from './sign-in-union' -export * from './sign-up-union' -export * from './update-user-password-union' -export * from './update-user-profile-union' -export * from './verify-phone-number-union' -export * from './verify-account-union' diff --git a/api-js/src/user/unions/remove-phone-number-union.js b/api-js/src/user/unions/remove-phone-number-union.js deleted file mode 100644 index 1af22a5d97..0000000000 --- a/api-js/src/user/unions/remove-phone-number-union.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLUnionType } from 'graphql' -import { - removePhoneNumberErrorType, - removePhoneNumberResultType, -} from '../objects' - -export const removePhoneNumberUnion = new GraphQLUnionType({ - name: 'RemovePhoneNumberUnion', - description: - 'This union is used with the `RemovePhoneNumber` mutation, allowing for users to remove their phone number, and support any errors that may occur', - types: [removePhoneNumberErrorType, removePhoneNumberResultType], - resolveType({ _type }) { - if (_type === 'result') { - return removePhoneNumberResultType - } else { - return removePhoneNumberErrorType - } - }, -}) diff --git a/api-js/src/user/unions/sign-up-union.js b/api-js/src/user/unions/sign-up-union.js deleted file mode 100644 index 72b4ec39dd..0000000000 --- a/api-js/src/user/unions/sign-up-union.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLUnionType } from 'graphql' -import { authResultType, signUpError } from '../objects' - -export const signUpUnion = new GraphQLUnionType({ - name: 'SignUpUnion', - description: - 'This union is used with the `signUp` mutation, allowing for the user to sign up, and support any errors that may occur.', - types: [authResultType, signUpError], - resolveType({ _type }) { - if (_type === 'authResult') { - return authResultType - } else { - return signUpError - } - }, -}) diff --git a/api-js/src/user/unions/update-user-password-union.js b/api-js/src/user/unions/update-user-password-union.js deleted file mode 100644 index f5b63b38b8..0000000000 --- a/api-js/src/user/unions/update-user-password-union.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLUnionType } from 'graphql' -import { - updateUserPasswordErrorType, - updateUserPasswordResultType, -} from '../objects' - -export const updateUserPasswordUnion = new GraphQLUnionType({ - name: 'UpdateUserPasswordUnion', - description: - 'This union is used with the `updateUserPassword` mutation, allowing for users to update their password, and support any errors that may occur', - types: [updateUserPasswordErrorType, updateUserPasswordResultType], - resolveType({ _type }) { - if (_type === 'regular') { - return updateUserPasswordResultType - } else { - return updateUserPasswordErrorType - } - }, -}) diff --git a/api-js/src/user/unions/update-user-profile-union.js b/api-js/src/user/unions/update-user-profile-union.js deleted file mode 100644 index ed95177ce6..0000000000 --- a/api-js/src/user/unions/update-user-profile-union.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLUnionType } from 'graphql' -import { - updateUserProfileErrorType, - updateUserProfileResultType, -} from '../objects' - -export const updateUserProfileUnion = new GraphQLUnionType({ - name: 'UpdateUserProfileUnion', - description: - 'This union is used with the `updateUserProfile` mutation, allowing for users to update their profile, and support any errors that may occur', - types: [updateUserProfileErrorType, updateUserProfileResultType], - resolveType({ _type }) { - if (_type === 'success') { - return updateUserProfileResultType - } else { - return updateUserProfileErrorType - } - }, -}) diff --git a/api-js/src/user/unions/verify-phone-number-union.js b/api-js/src/user/unions/verify-phone-number-union.js deleted file mode 100644 index 16ab4e3845..0000000000 --- a/api-js/src/user/unions/verify-phone-number-union.js +++ /dev/null @@ -1,19 +0,0 @@ -import { GraphQLUnionType } from 'graphql' -import { - verifyPhoneNumberErrorType, - verifyPhoneNumberResultType, -} from '../objects' - -export const verifyPhoneNumberUnion = new GraphQLUnionType({ - name: 'VerifyPhoneNumberUnion', - description: - 'This union is used with the `verifyPhoneNumber` mutation, allowing for users to verify their phone number, and support any errors that may occur', - types: [verifyPhoneNumberErrorType, verifyPhoneNumberResultType], - resolveType({ _type }) { - if (_type === 'success') { - return verifyPhoneNumberResultType - } else { - return verifyPhoneNumberErrorType - } - }, -}) diff --git a/api-js/src/validators/cleanse-input.js b/api-js/src/validators/cleanse-input.js deleted file mode 100644 index 3be8d9fedb..0000000000 --- a/api-js/src/validators/cleanse-input.js +++ /dev/null @@ -1,11 +0,0 @@ -import validator from 'validator' - -export const cleanseInput = (input) => { - if (typeof input !== 'string' && typeof input !== 'number') { - return '' - } - input = validator.trim(input) - input = validator.stripLow(input) - input = validator.escape(input) - return input -} diff --git a/api-js/src/validators/decrypt-phone-number.js b/api-js/src/validators/decrypt-phone-number.js deleted file mode 100644 index caa374a608..0000000000 --- a/api-js/src/validators/decrypt-phone-number.js +++ /dev/null @@ -1,16 +0,0 @@ -import crypto from 'crypto' - -const { CIPHER_KEY } = process.env - -export const decryptPhoneNumber = ({ iv, tag, phoneNumber: encrypted }) => { - const decipher = crypto.createDecipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(iv, 'hex'), - { authTagLength: 16 }, - ) - decipher.setAuthTag(Buffer.from(tag, 'hex')) - let decrypted = decipher.update(encrypted, 'hex', 'utf8') - decrypted += decipher.final('utf8') - return decrypted -} diff --git a/api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js b/api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js deleted file mode 100644 index b507d7fcf9..0000000000 --- a/api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { verifiedDomainOrder } from '../verified-domain-order' -import { OrderDirection, VerifiedDomainOrderField } from '../../../enums' - -describe('given the verifiedDomainOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = verifiedDomainOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = verifiedDomainOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(VerifiedDomainOrderField), - ) - }) - }) -}) diff --git a/api-js/src/verified-domains/queries/find-verified-domain-by-domain.js b/api-js/src/verified-domains/queries/find-verified-domain-by-domain.js deleted file mode 100644 index 80e159f3b8..0000000000 --- a/api-js/src/verified-domains/queries/find-verified-domain-by-domain.js +++ /dev/null @@ -1,40 +0,0 @@ -import { GraphQLNonNull } from 'graphql' -import { t } from '@lingui/macro' -import { Domain } from '../../scalars' -import { verifiedDomainType } from '../objects' - -export const findVerifiedDomainByDomain = { - type: verifiedDomainType, - description: 'Retrieve a specific verified domain by providing a domain.', - args: { - domain: { - type: GraphQLNonNull(Domain), - description: 'The domain you wish to retrieve information for.', - }, - }, - resolve: async ( - _, - args, - { - i18n, - loaders: { loadVerifiedDomainsById }, - validators: { cleanseInput }, - }, - ) => { - // Cleanse input - const domainInput = cleanseInput(args.domain) - - // Retrieve domain by domain - const domain = await loadVerifiedDomainsById.load(domainInput) - - if (typeof domain === 'undefined') { - console.warn(`User could not retrieve verified domain.`) - throw new Error( - i18n._(t`No verified domain with the provided domain could be found.`), - ) - } - - domain.id = domain._key - return domain - }, -} diff --git a/api-js/src/verified-organizations/index.js b/api-js/src/verified-organizations/index.js deleted file mode 100644 index 7dab4e57a0..0000000000 --- a/api-js/src/verified-organizations/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './loaders' -export * from './objects' -export * from './queries' diff --git a/api-js/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js b/api-js/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js deleted file mode 100644 index 69612d1e7c..0000000000 --- a/api-js/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { verifiedOrganizationOrder } from '../verified-organization-order' -import { OrderDirection, VerifiedOrganizationOrderField } from '../../../enums' - -describe('given the verifiedOrganizationOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = verifiedOrganizationOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = verifiedOrganizationOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject( - GraphQLNonNull(VerifiedOrganizationOrderField), - ) - }) - }) -}) diff --git a/api-js/src/verified-organizations/queries/find-verified-organization-by-slug.js b/api-js/src/verified-organizations/queries/find-verified-organization-by-slug.js deleted file mode 100644 index 04fddad420..0000000000 --- a/api-js/src/verified-organizations/queries/find-verified-organization-by-slug.js +++ /dev/null @@ -1,38 +0,0 @@ -import { GraphQLNonNull } from 'graphql' -import { t } from '@lingui/macro' -import { Slug } from '../../scalars' -import { verifiedOrganizationType } from '../objects' - -export const findVerifiedOrganizationBySlug = { - type: verifiedOrganizationType, - description: 'Select all information on a selected verified organization.', - args: { - orgSlug: { - type: GraphQLNonNull(Slug), - description: - 'The slugified organization name you want to retrieve data for.', - }, - }, - resolve: async ( - _, - args, - { i18n, loaders: { loadVerifiedOrgBySlug }, validators: { cleanseInput } }, - ) => { - // Cleanse input - const orgSlug = cleanseInput(args.orgSlug) - - // Retrieve organization by slug - const org = await loadVerifiedOrgBySlug.load(orgSlug) - - if (typeof org === 'undefined') { - console.warn(`User could not retrieve verified organization.`) - throw new Error( - i18n._( - t`No verified organization with the provided slug could be found.`, - ), - ) - } - - return org - }, -} diff --git a/api-js/src/web-scan/index.js b/api-js/src/web-scan/index.js deleted file mode 100644 index 20b59a8da4..0000000000 --- a/api-js/src/web-scan/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './loaders' -export * from './objects' -export * from './subscriptions' diff --git a/api-js/src/web-scan/inputs/__tests__/https-order.test.js b/api-js/src/web-scan/inputs/__tests__/https-order.test.js deleted file mode 100644 index b2bf051d78..0000000000 --- a/api-js/src/web-scan/inputs/__tests__/https-order.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { httpsOrder } from '../https-order' -import { OrderDirection, HttpsOrderField } from '../../../enums' - -describe('given the httpsOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = httpsOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = httpsOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject(GraphQLNonNull(HttpsOrderField)) - }) - }) -}) diff --git a/api-js/src/web-scan/inputs/__tests__/ssl-order.test.js b/api-js/src/web-scan/inputs/__tests__/ssl-order.test.js deleted file mode 100644 index 9085ff95fd..0000000000 --- a/api-js/src/web-scan/inputs/__tests__/ssl-order.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { GraphQLNonNull } from 'graphql' - -import { sslOrder } from '../ssl-order' -import { OrderDirection, SslOrderField } from '../../../enums' - -describe('given the sslOrder input object', () => { - describe('testing fields', () => { - it('has a direction field', () => { - const demoType = sslOrder.getFields() - - expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) - }) - it('has a field field', () => { - const demoType = sslOrder.getFields() - - expect(demoType).toHaveProperty('field') - expect(demoType.field.type).toMatchObject(GraphQLNonNull(SslOrderField)) - }) - }) -}) diff --git a/api-js/src/web-scan/inputs/https-order.js b/api-js/src/web-scan/inputs/https-order.js deleted file mode 100644 index 706d82eb93..0000000000 --- a/api-js/src/web-scan/inputs/https-order.js +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' - -import { OrderDirection, HttpsOrderField } from '../../enums' - -export const httpsOrder = new GraphQLInputObjectType({ - name: 'HTTPSOrder', - description: 'Ordering options for HTTPS connections.', - fields: { - field: { - type: GraphQLNonNull(HttpsOrderField), - description: 'The field to order HTTPS edges by.', - }, - direction: { - type: GraphQLNonNull(OrderDirection), - description: 'The ordering direction.', - }, - }, -}) diff --git a/api-js/src/web-scan/inputs/index.js b/api-js/src/web-scan/inputs/index.js deleted file mode 100644 index 24e44e80ff..0000000000 --- a/api-js/src/web-scan/inputs/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './https-order' -export * from './ssl-order' diff --git a/api-js/src/web-scan/inputs/ssl-order.js b/api-js/src/web-scan/inputs/ssl-order.js deleted file mode 100644 index d2d489f5ad..0000000000 --- a/api-js/src/web-scan/inputs/ssl-order.js +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' - -import { OrderDirection, SslOrderField } from '../../enums' - -export const sslOrder = new GraphQLInputObjectType({ - name: 'SSLOrder', - description: 'Ordering options for SSL connections.', - fields: { - field: { - type: GraphQLNonNull(SslOrderField), - description: 'The field to order SSL edges by.', - }, - direction: { - type: GraphQLNonNull(OrderDirection), - description: 'The ordering direction.', - }, - }, -}) diff --git a/api-js/src/web-scan/loaders/__tests__/load-https-by-key.test.js b/api-js/src/web-scan/loaders/__tests__/load-https-by-key.test.js deleted file mode 100644 index 7ec7f8ef1a..0000000000 --- a/api-js/src/web-scan/loaders/__tests__/load-https-by-key.test.js +++ /dev/null @@ -1,214 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadHttpsByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadHttpsByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.https.save({}) - await collections.https.save({}) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single https scan', async () => { - const expectedCursor = await query` - FOR httpsScan IN https - SORT httpsScan._key ASC LIMIT 1 - RETURN MERGE({ id: httpsScan._key, _type: "https" }, httpsScan) - ` - const expectedHttps = await expectedCursor.next() - - const loader = loadHttpsByKey({ query }) - const https = await loader.load(expectedHttps._key) - - expect(https).toEqual(expectedHttps) - }) - }) - describe('given multiple ids', () => { - it('returns multiple https scans', async () => { - const httpsKeys = [] - const expectedHttpsScans = [] - - const expectedCursor = await query` - FOR httpsScan IN https - RETURN MERGE({ id: httpsScan._key, _type: "https" }, httpsScan) - ` - - while (expectedCursor.hasMore) { - const tempHttps = await expectedCursor.next() - httpsKeys.push(tempHttps._key) - expectedHttpsScans.push(tempHttps) - } - - const loader = loadHttpsByKey({ query }) - const httpsScans = await loader.loadMany(httpsKeys) - - expect(httpsScans).toEqual(expectedHttpsScans) - }) - }) - }) - describe('given an unsuccessful load', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadHttpsByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find HTTPS scan(s). Please try again.'), - ) - } - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadHttpsByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadHttpsByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to load HTTPS scan(s). Please try again.'), - ) - } - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadHttpsByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadHttpsByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.', - ), - ) - } - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadHttpsByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadHttpsByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.', - ), - ) - } - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadHttpsByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/loaders/__tests__/load-https-connections-by-domain-id.test.js b/api-js/src/web-scan/loaders/__tests__/load-https-connections-by-domain-id.test.js deleted file mode 100644 index 14e55ef8f4..0000000000 --- a/api-js/src/web-scan/loaders/__tests__/load-https-connections-by-domain-id.test.js +++ /dev/null @@ -1,1828 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { loadHttpsConnectionsByDomainId, loadHttpsByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the load https connection function', () => { - let query, - drop, - truncate, - collections, - user, - domain, - i18n, - httpsScan1, - httpsScan2 - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - httpsScan1 = await collections.https.save({ - timestamp: '2020-10-02T12:43:39Z', - }) - httpsScan2 = await collections.https.save({ - timestamp: '2020-10-03T12:43:39Z', - }) - await collections.domainsHTTPS.save({ - _to: httpsScan1._id, - _from: domain._id, - }) - await collections.domainsHTTPS.save({ - _to: httpsScan2._id, - _from: domain._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('using after cursor', () => { - it('returns https scan(s) after a given node id', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan1._key, - httpsScan2._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - expectedHttpsScans[1].id = expectedHttpsScans[1]._key - expectedHttpsScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - after: toGlobalId('https', expectedHttpsScans[0]._key), - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[1]._key), - node: { - ...expectedHttpsScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScans[1]._key), - endCursor: toGlobalId('https', expectedHttpsScans[1]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns https scan(s) before a given node id', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan1._key, - httpsScan2._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - expectedHttpsScans[1].id = expectedHttpsScans[1]._key - expectedHttpsScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - before: toGlobalId('https', expectedHttpsScans[1]._key), - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[0]._key), - node: { - ...expectedHttpsScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('https', expectedHttpsScans[0]._key), - endCursor: toGlobalId('https', expectedHttpsScans[0]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of https scan(s)', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan1._key, - httpsScan2._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - expectedHttpsScans[1].id = expectedHttpsScans[1]._key - expectedHttpsScans[1].domainId = domain._id - - const connectionArgs = { - first: 1, - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[0]._key), - node: { - ...expectedHttpsScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('https', expectedHttpsScans[0]._key), - endCursor: toGlobalId('https', expectedHttpsScans[0]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of https scan(s)', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan1._key, - httpsScan2._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - expectedHttpsScans[1].id = expectedHttpsScans[1]._key - expectedHttpsScans[1].domainId = domain._id - - const connectionArgs = { - last: 1, - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[1]._key), - node: { - ...expectedHttpsScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScans[1]._key), - endCursor: toGlobalId('https', expectedHttpsScans[1]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('using date filter', () => { - let httpsScan3 - beforeEach(async () => { - httpsScan3 = await collections.https.save({ - timestamp: '2020-10-04T12:43:39Z', - }) - await collections.domainsHTTPS.save({ - _to: httpsScan3._id, - _from: domain._id, - }) - }) - describe('using start date filter', () => { - it('returns https scans at and after the start date', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan2._key, - httpsScan3._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - expectedHttpsScans[1].id = expectedHttpsScans[1]._key - expectedHttpsScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03', - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[0]._key), - node: { - ...expectedHttpsScans[0], - }, - }, - { - cursor: toGlobalId('https', expectedHttpsScans[1]._key), - node: { - ...expectedHttpsScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScans[0]._key), - endCursor: toGlobalId('https', expectedHttpsScans[1]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('using end date filter', () => { - it('returns https scans at and before the end date', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = await loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan1._key, - httpsScan2._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - expectedHttpsScans[1].id = expectedHttpsScans[1]._key - expectedHttpsScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - endDate: '2020-10-03T13:50:00Z', - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[0]._key), - node: { - ...expectedHttpsScans[0], - }, - }, - { - cursor: toGlobalId('https', expectedHttpsScans[1]._key), - node: { - ...expectedHttpsScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('https', expectedHttpsScans[0]._key), - endCursor: toGlobalId('https', expectedHttpsScans[1]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('using start and end date filters', () => { - it('returns a scan on a specific date', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const httpsLoader = loadHttpsByKey({ query, i18n }) - const expectedHttpsScans = await httpsLoader.loadMany([ - httpsScan2._key, - ]) - - expectedHttpsScans[0].id = expectedHttpsScans[0]._key - expectedHttpsScans[0].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03T00:00:00Z', - endDate: '2020-10-03T23:59:59Z', - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScans[0]._key), - node: { - ...expectedHttpsScans[0], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScans[0]._key), - endCursor: toGlobalId('https', expectedHttpsScans[0]._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('using the orderBy field', () => { - let httpsOne, httpsTwo, httpsThree - beforeEach(async () => { - await truncate() - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - httpsOne = await collections.https.save({ - timestamp: '2021-01-26 23:24:34.506578Z', - implementation: 'Bad Chain', - enforced: 'Moderate', - hsts: 'HSTS Fully Implemented', - hstsAge: 31536000, - preloaded: 'HSTS Not Preloaded', - }) - httpsTwo = await collections.https.save({ - timestamp: '2021-01-27 23:24:34.506578Z', - implementation: 'Bad Hostname', - enforced: 'Strict', - hsts: 'HSTS Max Age Too Short', - hstsAge: 31536001, - preloaded: 'HSTS Preload Ready', - }) - httpsThree = await collections.https.save({ - timestamp: '2021-01-28 23:24:34.506578Z', - implementation: 'Valid HTTPS', - enforced: 'Weak', - hsts: 'No HSTS', - hstsAge: 31536002, - preloaded: 'HSTS Preloaded', - }) - await collections.domainsHTTPS.save({ - _to: httpsOne._id, - _from: domain._id, - }) - await collections.domainsHTTPS.save({ - _to: httpsTwo._id, - _from: domain._id, - }) - await collections.domainsHTTPS.save({ - _to: httpsThree._id, - _from: domain._id, - }) - }) - describe('ordering on TIMESTAMP', () => { - describe('direction is set to ASC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsOne._key), - before: toGlobalId('https', httpsThree._key), - orderBy: { - field: 'timestamp', - direction: 'ASC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsThree._key), - before: toGlobalId('https', httpsOne._key), - orderBy: { - field: 'timestamp', - direction: 'DESC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('order on IMPLEMENTATION', () => { - describe('direction is set to ASC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsOne._key), - before: toGlobalId('https', httpsThree._key), - orderBy: { - field: 'implementation', - direction: 'ASC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsThree._key), - before: toGlobalId('https', httpsOne._key), - orderBy: { - field: 'implementation', - direction: 'DESC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on ENFORCED', () => { - describe('direction is set to ASC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsOne._key), - before: toGlobalId('https', httpsThree._key), - orderBy: { - field: 'enforced', - direction: 'ASC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsThree._key), - before: toGlobalId('https', httpsOne._key), - orderBy: { - field: 'enforced', - direction: 'DESC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on HSTS', () => { - describe('direction is set to ASC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsOne._key), - before: toGlobalId('https', httpsThree._key), - orderBy: { - field: 'hsts', - direction: 'ASC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsThree._key), - before: toGlobalId('https', httpsOne._key), - orderBy: { - field: 'hsts', - direction: 'DESC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on HSTS_AGE', () => { - describe('direction is set to ASC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsOne._key), - before: toGlobalId('https', httpsThree._key), - orderBy: { - field: 'hsts-age', - direction: 'ASC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsThree._key), - before: toGlobalId('https', httpsOne._key), - orderBy: { - field: 'hsts-age', - direction: 'DESC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on PRELOADED', () => { - describe('direction is set to ASC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsOne._key), - before: toGlobalId('https', httpsThree._key), - orderBy: { - field: 'preloaded', - direction: 'ASC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - describe('direction is set to DESC', () => { - it('returns https scans', async () => { - const loader = loadHttpsByKey({ query, userKey: user._key, i18n }) - const expectedHttpsScan = await loader.load(httpsTwo._key) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('https', httpsThree._key), - before: toGlobalId('https', httpsOne._key), - orderBy: { - field: 'preloaded', - direction: 'DESC', - }, - } - - const httpsScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('https', expectedHttpsScan._key), - node: { - domainId: domain._id, - ...expectedHttpsScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('https', expectedHttpsScan._key), - endCursor: toGlobalId('https', expectedHttpsScan._key), - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no https scans are found', () => { - it('returns an empty structure', async () => { - await truncate() - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - const httpsScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(httpsScans).toEqual(expectedStructure) - }) - }) - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given an unsuccessful load', () => { - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} tried to have \`first\` and \`last\` arguments set for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `HTTPS` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `HTTPS` connection cannot be less than zero.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 101, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 101 records on the `HTTPS` connection exceeds the `first` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 101 for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 500 records on the `HTTPS` connection exceeds the `last` limit of 100 records.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load HTTPS scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get https information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load HTTPS scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get https information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given an unsuccessful load', () => { - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.", - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} tried to have \`first\` and \`last\` arguments set for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('limits are below minimum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `HTTPS` ne peut être inférieur à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `HTTPS` ne peut être inférieur à zéro.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are above maximum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 101, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 101 enregistrements sur la connexion `HTTPS` dépasse la limite `first` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 101 for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 500 enregistrements sur la connexion `HTTPS` dépasse la limite `last` de 100 enregistrements.', - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadHttpsConnectionsByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get https information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadHttpsConnectionsByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get https information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/loaders/__tests__/load-ssl-by-key.test.js b/api-js/src/web-scan/loaders/__tests__/load-ssl-by-key.test.js deleted file mode 100644 index 514a097476..0000000000 --- a/api-js/src/web-scan/loaders/__tests__/load-ssl-by-key.test.js +++ /dev/null @@ -1,214 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { loadSslByKey } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the loadSslByKey function', () => { - let query, drop, truncate, collections, i18n - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - afterEach(() => { - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await collections.ssl.save({}) - await collections.ssl.save({}) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('given a single id', () => { - it('returns a single ssl scan', async () => { - const expectedCursor = await query` - FOR sslScan IN ssl - SORT sslScan._key ASC LIMIT 1 - RETURN MERGE({ id: sslScan._key, _type: "ssl" }, sslScan) - ` - const expectedSsl = await expectedCursor.next() - - const loader = loadSslByKey({ query, i18n }) - const ssl = await loader.load(expectedSsl._key) - - expect(ssl).toEqual(expectedSsl) - }) - }) - describe('given multiple ids', () => { - it('returns multiple ssl scans', async () => { - const sslKeys = [] - const expectedSslScans = [] - - const expectedCursor = await query` - FOR sslScan IN ssl - RETURN MERGE({ id: sslScan._key, _type: "ssl" }, sslScan) - ` - - while (expectedCursor.hasMore) { - const tempSsl = await expectedCursor.next() - sslKeys.push(tempSsl._key) - expectedSslScans.push(tempSsl) - } - - const loader = loadSslByKey({ query, i18n }) - const sslScans = await loader.loadMany(sslKeys) - - expect(sslScans).toEqual(expectedSslScans) - }) - }) - }) - describe('given an unsuccessful load', () => { - describe('language set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadSslByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find SSL scan(s). Please try again.'), - ) - } - expect(consoleErrorOutput).toEqual([ - `Database error occurred when user: 1234 running loadSslByKey: Error: Database error occurred.`, - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadSslByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error('Unable to find SSL scan(s). Please try again.'), - ) - } - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSslByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('language set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given a database error', () => { - it('raises an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - const loader = loadSslByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.', - ), - ) - } - expect(consoleErrorOutput).toEqual([ - 'Database error occurred when user: 1234 running loadSslByKey: Error: Database error occurred.', - ]) - }) - }) - describe('given a cursor error', () => { - it('raises an error', async () => { - const cursor = { - forEach() { - throw new Error('Cursor error occurred.') - }, - } - query = jest.fn().mockReturnValue(cursor) - const loader = loadSslByKey({ query, userKey: '1234', i18n }) - - try { - await loader.load('1') - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.', - ), - ) - } - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred when user: 1234 running loadSslByKey: Error: Cursor error occurred.`, - ]) - }) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/loaders/__tests__/load-ssl-connections-by-domain-id.test.js b/api-js/src/web-scan/loaders/__tests__/load-ssl-connections-by-domain-id.test.js deleted file mode 100644 index d655e21917..0000000000 --- a/api-js/src/web-scan/loaders/__tests__/load-ssl-connections-by-domain-id.test.js +++ /dev/null @@ -1,2225 +0,0 @@ -import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' -import { setupI18n } from '@lingui/core' - -import englishMessages from '../../../locale/en/messages' -import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { loadSslByKey, loadSslConnectionByDomainId } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the load ssl connection function', () => { - let query, drop, truncate, collections, user, domain, i18n, sslScan1, sslScan2 - - const consoleWarnOutput = [] - const mockedWarn = (output) => consoleWarnOutput.push(output) - - const consoleErrorOutput = [] - const mockedError = (output) => consoleErrorOutput.push(output) - - beforeAll(() => { - console.warn = mockedWarn - console.error = mockedError - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - - afterEach(() => { - consoleWarnOutput.length = 0 - consoleErrorOutput.length = 0 - }) - - describe('given a successful load', () => { - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - sslScan1 = await collections.ssl.save({ - timestamp: '2020-10-02T12:43:39Z', - }) - sslScan2 = await collections.ssl.save({ - timestamp: '2020-10-03T12:43:39Z', - }) - await collections.domainsSSL.save({ - _to: sslScan1._id, - _from: domain._id, - }) - await collections.domainsSSL.save({ - _to: sslScan2._id, - _from: domain._id, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - describe('using after cursor', () => { - it('returns ssl scan(s) after a given node id', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query, i18n }) - const expectedSslScans = await sslLoader.loadMany([ - sslScan1._key, - sslScan2._key, - ]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - expectedSslScans[1].id = expectedSslScans[1]._key - expectedSslScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - after: toGlobalId('ssl', expectedSslScans[0]._key), - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[1]._key), - node: { - ...expectedSslScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScans[1]._key), - endCursor: toGlobalId('ssl', expectedSslScans[1]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('using before cursor', () => { - it('returns ssl scan(s) before a given node id', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query, i18n }) - const expectedSslScans = await sslLoader.loadMany([ - sslScan1._key, - sslScan2._key, - ]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - expectedSslScans[1].id = expectedSslScans[1]._key - expectedSslScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - before: toGlobalId('ssl', expectedSslScans[1]._key), - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[0]._key), - node: { - ...expectedSslScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('ssl', expectedSslScans[0]._key), - endCursor: toGlobalId('ssl', expectedSslScans[0]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('using first limit', () => { - it('returns the first n amount of ssl scan(s)', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query, i18n }) - const expectedSslScans = await sslLoader.loadMany([ - sslScan1._key, - sslScan2._key, - ]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - expectedSslScans[1].id = expectedSslScans[1]._key - expectedSslScans[1].domainId = domain._id - - const connectionArgs = { - first: 1, - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[0]._key), - node: { - ...expectedSslScans[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('ssl', expectedSslScans[0]._key), - endCursor: toGlobalId('ssl', expectedSslScans[0]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('using last limit', () => { - it('returns the last n amount of ssl scan(s)', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query, i18n }) - const expectedSslScans = await sslLoader.loadMany([ - sslScan1._key, - sslScan2._key, - ]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - expectedSslScans[1].id = expectedSslScans[1]._key - expectedSslScans[1].domainId = domain._id - - const connectionArgs = { - last: 1, - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[1]._key), - node: { - ...expectedSslScans[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScans[1]._key), - endCursor: toGlobalId('ssl', expectedSslScans[1]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('using date filter', () => { - let sslScan3 - beforeEach(async () => { - sslScan3 = await collections.ssl.save({ - timestamp: '2020-10-04T12:43:39Z', - }) - await collections.domainsSSL.save({ - _to: sslScan3._id, - _from: domain._id, - }) - }) - describe('using start date filter', () => { - it('returns ssl scans at and after the start date', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query }) - const expectedSslScans = await sslLoader.loadMany([ - sslScan2._key, - sslScan3._key, - ]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - expectedSslScans[1].id = expectedSslScans[1]._key - expectedSslScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03', - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[0]._key), - node: { - ...expectedSslScans[0], - }, - }, - { - cursor: toGlobalId('ssl', expectedSslScans[1]._key), - node: { - ...expectedSslScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScans[0]._key), - endCursor: toGlobalId('ssl', expectedSslScans[1]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('using end date filter', () => { - it('returns ssl scans at and before the end date', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query, i18n }) - const expectedSslScans = await sslLoader.loadMany([ - sslScan1._key, - sslScan2._key, - ]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - expectedSslScans[1].id = expectedSslScans[1]._key - expectedSslScans[1].domainId = domain._id - - const connectionArgs = { - first: 5, - endDate: '2020-10-03T13:50:00Z', - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[0]._key), - node: { - ...expectedSslScans[0], - }, - }, - { - cursor: toGlobalId('ssl', expectedSslScans[1]._key), - node: { - ...expectedSslScans[1], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('ssl', expectedSslScans[0]._key), - endCursor: toGlobalId('ssl', expectedSslScans[1]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('using start and end date filters', () => { - it('returns a scan on a specific date', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const sslLoader = loadSslByKey({ query, i18n }) - const expectedSslScans = await sslLoader.loadMany([sslScan2._key]) - - expectedSslScans[0].id = expectedSslScans[0]._key - expectedSslScans[0].domainId = domain._id - - const connectionArgs = { - first: 5, - startDate: '2020-10-03T00:00:00Z', - endDate: '2020-10-03T23:59:59Z', - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScans[0]._key), - node: { - ...expectedSslScans[0], - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScans[0]._key), - endCursor: toGlobalId('ssl', expectedSslScans[0]._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('using the orderBy field', () => { - let sslOne, sslTwo, sslThree - beforeEach(async () => { - await truncate() - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - }) - sslOne = await collections.ssl.save({ - acceptable_ciphers: ['a'], - acceptable_curves: ['a'], - ccs_injection_vulnerable: false, - heartbleed_vulnerable: false, - strong_ciphers: ['a'], - strong_curves: ['a'], - supports_ecdh_key_exchange: false, - timestamp: '2021-01-26 23:29:19.652119', - weak_ciphers: ['a'], - weak_curves: ['a'], - }) - sslTwo = await collections.ssl.save({ - acceptable_ciphers: ['b'], - acceptable_curves: ['b'], - ccs_injection_vulnerable: false, - heartbleed_vulnerable: false, - strong_ciphers: ['b'], - strong_curves: ['b'], - supports_ecdh_key_exchange: false, - timestamp: '2021-01-27 23:29:19.652119', - weak_ciphers: ['b'], - weak_curves: ['b'], - }) - sslThree = await collections.ssl.save({ - acceptable_ciphers: ['c'], - acceptable_curves: ['c'], - ccs_injection_vulnerable: false, - heartbleed_vulnerable: false, - strong_ciphers: ['c'], - strong_curves: ['c'], - supports_ecdh_key_exchange: false, - timestamp: '2021-01-28 23:29:19.652119', - weak_ciphers: ['c'], - weak_curves: ['c'], - }) - await collections.domainsSSL.save({ - _to: sslOne._id, - _from: domain._id, - }) - await collections.domainsSSL.save({ - _to: sslTwo._id, - _from: domain._id, - }) - await collections.domainsSSL.save({ - _to: sslThree._id, - _from: domain._id, - }) - }) - describe('ordering on ACCEPTABLE_CIPHERS', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'acceptable-ciphers', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'acceptable-ciphers', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on ACCEPTABLE_CURVES', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'acceptable-curves', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'acceptable-curves', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on CCS_INJECTION_VULNERABLE', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'ccs-injection-vulnerable', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'ccs-injection-vulnerable', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on HEARTBLEED_VULNERABLE', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'heartbleed-vulnerable', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'heartbleed-vulnerable', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on STRONG_CIPHERS', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'strong-ciphers', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'strong-ciphers', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on STRONG_CURVES', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'strong-curves', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'strong-curves', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on SUPPORTS_ECDH_KEY_EXCHANGE', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'supports-ecdh-key-exchange', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'supports-ecdh-key-exchange', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on TIMESTAMP', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'timestamp', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'timestamp', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on WEAK_CIPHERS', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'weak-ciphers', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'weak-ciphers', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on WEAK_CURVES', () => { - describe('direction is set to ASC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslOne._key), - before: toGlobalId('ssl', sslThree._key), - orderBy: { - field: 'weak-curves', - direction: 'ASC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - describe('ordering is set to DESC', () => { - it('returns ssl scan', async () => { - const loader = loadSslByKey({ query, userKey: user._key, i18n }) - const expectedSslScan = await loader.load(sslTwo._key) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - domainId: domain._id, - first: 5, - after: toGlobalId('ssl', sslThree._key), - before: toGlobalId('ssl', sslOne._key), - orderBy: { - field: 'weak-curves', - direction: 'DESC', - }, - } - - const sslScans = await connectionLoader(connectionArgs) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('ssl', expectedSslScan._key), - node: { - domainId: domain._id, - ...expectedSslScan, - }, - }, - ], - totalCount: 3, - pageInfo: { - hasNextPage: true, - hasPreviousPage: true, - startCursor: toGlobalId('ssl', expectedSslScan._key), - endCursor: toGlobalId('ssl', expectedSslScan._key), - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - }) - describe('no ssl scans are found', () => { - it('returns an empty structure', async () => { - await truncate() - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - const sslScans = await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - - expect(sslScans).toEqual(expectedStructure) - }) - }) - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given an unsuccessful load', () => { - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'You must provide a `first` or `last` value to properly paginate the `SSL` connection.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `SSL` connection is not supported.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} tried to have \`first\` and \`last\` arguments set for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('both limits are below minimum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `SSL` connection cannot be less than zero.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `SSL` connection cannot be less than zero.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - describe('both limits are above maximum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 101, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 101 records on the `SSL` connection exceeds the `first` limit of 100 records.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 101 for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Requesting 500 records on the `SSL` connection exceeds the `last` limit of 100 records.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load SSL scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get ssl information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error('Unable to load SSL scan(s). Please try again.'), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get ssl information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('given an unsuccessful load', () => { - describe('both limits are not set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = {} - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('first and last arguments are set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 1, - last: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.", - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} tried to have \`first\` and \`last\` arguments set for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('both limits are below minimum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: -1, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `SSL` ne peut être inférieur à zéro.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set below zero for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: -5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `SSL` ne peut être inférieur à zéro.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set below zero for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - describe('both limits are above maximum', () => { - describe('first limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 101, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 101 enregistrements sur la connexion `SSL` dépasse la limite `first` de 100 enregistrements.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`first\` set to 101 for: loadSslConnectionByDomainId.`, - ]) - }) - }) - describe('last limit is set', () => { - it('returns an error message', async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: 500, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'La demande de 500 enregistrements sur la connexion `SSL` dépasse la limite `last` de 100 enregistrements.', - ), - ) - } - - expect(consoleWarnOutput).toEqual([ - `User: ${user._key} attempted to have \`last\` set to 500 for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - describe('limits are not set to numbers', () => { - describe('first limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`first\` set as a ${typeof invalidInput} for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - describe('last limit is set', () => { - ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - last: invalidInput, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), - ) - } - expect(consoleWarnOutput).toEqual([ - `User: ${ - user._key - } attempted to have \`last\` set as a ${typeof invalidInput} for: loadSslConnectionByDomainId.`, - ]) - }) - }) - }) - }) - }) - describe('database error occurs', () => { - it('throws an error', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Database error occurred while user: ${user._key} was trying to get ssl information for ${domain._id}, error: Error: Database Error Occurred.`, - ]) - }) - }) - describe('cursor error occurs', () => { - it('throws an error', async () => { - const cursor = { - next() { - throw new Error('Cursor Error Occurred.') - }, - } - const query = jest.fn().mockReturnValueOnce(cursor) - - const connectionLoader = loadSslConnectionByDomainId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }) - - const connectionArgs = { - first: 5, - } - - try { - await connectionLoader({ - domainId: domain._id, - ...connectionArgs, - }) - } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.', - ), - ) - } - - expect(consoleErrorOutput).toEqual([ - `Cursor error occurred while user: ${user._key} was trying to get ssl information for ${domain._id}, error: Error: Cursor Error Occurred.`, - ]) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/loaders/index.js b/api-js/src/web-scan/loaders/index.js deleted file mode 100644 index badd1c0d59..0000000000 --- a/api-js/src/web-scan/loaders/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './load-https-by-key' -export * from './load-https-connections-by-domain-id' -export * from './load-ssl-by-key' -export * from './load-ssl-connections-by-domain-id' diff --git a/api-js/src/web-scan/loaders/load-https-by-key.js b/api-js/src/web-scan/loaders/load-https-by-key.js deleted file mode 100644 index eda0c92941..0000000000 --- a/api-js/src/web-scan/loaders/load-https-by-key.js +++ /dev/null @@ -1,38 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadHttpsByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - try { - cursor = await query` - WITH https - FOR httpsScan IN https - FILTER httpsScan._key IN ${keys} - RETURN MERGE({ id: httpsScan._key, _type: "https" }, httpsScan) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadHttpsByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to find HTTPS scan(s). Please try again.`), - ) - } - - const httpsMap = {} - try { - await cursor.forEach((httpsScan) => { - httpsMap[httpsScan._key] = httpsScan - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadHttpsByKey: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load HTTPS scan(s). Please try again.`), - ) - } - - return keys.map((key) => httpsMap[key]) - }) diff --git a/api-js/src/web-scan/loaders/load-https-connections-by-domain-id.js b/api-js/src/web-scan/loaders/load-https-connections-by-domain-id.js deleted file mode 100644 index a368721e18..0000000000 --- a/api-js/src/web-scan/loaders/load-https-connections-by-domain-id.js +++ /dev/null @@ -1,380 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadHttpsConnectionsByDomainId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ - domainId, - startDate, - endDate, - after, - before, - first, - last, - orderBy, - }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(httpsScan._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(https, ${afterId})` - - let documentField = aql`` - let httpsField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - documentField = aql`afterVar.timestamp` - httpsField = aql`httpsScan.timestamp` - } else if (orderBy.field === 'implementation') { - documentField = aql`afterVar.implementation` - httpsField = aql`httpsScan.implementation` - } else if (orderBy.field === 'enforced') { - documentField = aql`afterVar.enforced` - httpsField = aql`httpsScan.enforced` - } else if (orderBy.field === 'hsts') { - documentField = aql`afterVar.hsts` - httpsField = aql`httpsScan.hsts` - } else if (orderBy.field === 'hsts-age') { - documentField = aql`afterVar.hstsAge` - httpsField = aql`httpsScan.hstsAge` - } else if (orderBy.field === 'preloaded') { - documentField = aql`afterVar.preloaded` - httpsField = aql`httpsScan.preloaded` - } - - afterTemplate = aql` - FILTER ${httpsField} ${afterTemplateDirection} ${documentField} - OR (${httpsField} == ${documentField} - AND TO_NUMBER(httpsScan._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(httpsScan._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(https, ${beforeId})` - - let documentField = aql`` - let httpsField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - documentField = aql`beforeVar.timestamp` - httpsField = aql`httpsScan.timestamp` - } else if (orderBy.field === 'implementation') { - documentField = aql`beforeVar.implementation` - httpsField = aql`httpsScan.implementation` - } else if (orderBy.field === 'enforced') { - documentField = aql`beforeVar.enforced` - httpsField = aql`httpsScan.enforced` - } else if (orderBy.field === 'hsts') { - documentField = aql`beforeVar.hsts` - httpsField = aql`httpsScan.hsts` - } else if (orderBy.field === 'hsts-age') { - documentField = aql`beforeVar.hstsAge` - httpsField = aql`httpsScan.hstsAge` - } else if (orderBy.field === 'preloaded') { - documentField = aql`beforeVar.preloaded` - httpsField = aql`httpsScan.preloaded` - } - - beforeTemplate = aql` - FILTER ${httpsField} ${beforeTemplateDirection} ${documentField} - OR (${httpsField} == ${documentField} - AND TO_NUMBER(httpsScan._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let startDateTemplate = aql`` - if (typeof startDate !== 'undefined') { - startDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(httpsScan.timestamp), - "%y-%m-%d" - ) >= - DATE_FORMAT( - DATE_TIMESTAMP(${startDate}), - "%y-%m-%d" - ) - ` - } - - let endDateTemplate = aql`` - if (typeof endDate !== 'undefined') { - endDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(httpsScan.timestamp), - "%y-%m-%d" - ) <= - DATE_FORMAT( - DATE_TIMESTAMP(${endDate}), - "%y-%m-%d" - ) - ` - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadHttpsConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`HTTPS\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} tried to have \`first\` and \`last\` arguments set for: loadHttpsConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`HTTPS\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadHttpsConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`HTTPS\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadHttpsConnectionsByDomainId.`, - ) - throw new Error( - i18n._( - t`Requesting ${amount} records on the \`HTTPS\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(httpsScan._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(httpsScan._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadHttpsConnectionsByDomainId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(httpsScan._key) > TO_NUMBER(LAST(retrievedHttps)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(httpsScan._key) < TO_NUMBER(FIRST(retrievedHttps)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - let httpsField = aql`` - let hasNextPageDocumentField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - httpsField = aql`httpsScan.timestamp` - hasNextPageDocumentField = aql`LAST(retrievedHttps).timestamp` - hasPreviousPageDocumentField = aql`FIRST(retrievedHttps).timestamp` - } else if (orderBy.field === 'implementation') { - httpsField = aql`httpsScan.implementation` - hasNextPageDocumentField = aql`LAST(retrievedHttps).implementation` - hasPreviousPageDocumentField = aql`FIRST(retrievedHttps).implementation` - } else if (orderBy.field === 'enforced') { - httpsField = aql`httpsScan.enforced` - hasNextPageDocumentField = aql`LAST(retrievedHttps).enforced` - hasPreviousPageDocumentField = aql`FIRST(retrievedHttps).enforced` - } else if (orderBy.field === 'hsts') { - httpsField = aql`httpsScan.hsts` - hasNextPageDocumentField = aql`LAST(retrievedHttps).hsts` - hasPreviousPageDocumentField = aql`FIRST(retrievedHttps).hsts` - } else if (orderBy.field === 'hsts-age') { - httpsField = aql`httpsScan.hstsAge` - hasNextPageDocumentField = aql`LAST(retrievedHttps).hstsAge` - hasPreviousPageDocumentField = aql`FIRST(retrievedHttps).hstsAge` - } else if (orderBy.field === 'preloaded') { - httpsField = aql`httpsScan.preloaded` - hasNextPageDocumentField = aql`LAST(retrievedHttps).preloaded` - hasPreviousPageDocumentField = aql`FIRST(retrievedHttps).preloaded` - } - - hasNextPageFilter = aql` - FILTER ${httpsField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${httpsField} == ${hasNextPageDocumentField} - AND TO_NUMBER(httpsScan._key) > TO_NUMBER(LAST(retrievedHttps)._key)) - ` - hasPreviousPageFilter = aql` - FILTER ${httpsField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${httpsField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(httpsScan._key) < TO_NUMBER(FIRST(retrievedHttps)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'timestamp') { - sortByField = aql`httpsScan.timestamp ${orderBy.direction},` - } else if (orderBy.field === 'implementation') { - sortByField = aql`httpsScan.implementation ${orderBy.direction},` - } else if (orderBy.field === 'enforced') { - sortByField = aql`httpsScan.enforced ${orderBy.direction},` - } else if (orderBy.field === 'hsts') { - sortByField = aql`httpsScan.hsts ${orderBy.direction},` - } else if (orderBy.field === 'hsts-age') { - sortByField = aql`httpsScan.hstsAge ${orderBy.direction},` - } else if (orderBy.field === 'preloaded') { - sortByField = aql`httpsScan.preloaded ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let requestedHttpsInfo - try { - requestedHttpsInfo = await query` - WITH domains, domainsHTTPS, https - LET httpsKeys = ( - FOR v, e IN 1 OUTBOUND ${domainId} domainsHTTPS - OPTIONS {bfs: true} - RETURN v._key - ) - - ${afterVar} - ${beforeVar} - - LET retrievedHttps = ( - FOR httpsScan IN https - FILTER httpsScan._key IN httpsKeys - ${afterTemplate} - ${beforeTemplate} - ${startDateTemplate} - ${endDateTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: httpsScan._key, _type: "https" }, httpsScan) - ) - - LET hasNextPage = (LENGTH( - FOR httpsScan IN https - FILTER httpsScan._key IN httpsKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(httpsScan._key) ${sortString} LIMIT 1 - RETURN httpsScan - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR httpsScan IN https - FILTER httpsScan._key IN httpsKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(httpsScan._key) ${sortString} LIMIT 1 - RETURN httpsScan - ) > 0 ? true : false) - - RETURN { - "httpsScans": retrievedHttps, - "totalCount": LENGTH(httpsKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedHttps)._key, - "endKey": LAST(retrievedHttps)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to get https information for ${domainId}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load HTTPS scan(s). Please try again.`), - ) - } - - let httpsScanInfo - try { - httpsScanInfo = await requestedHttpsInfo.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to get https information for ${domainId}, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load HTTPS scan(s). Please try again.`), - ) - } - - if (httpsScanInfo.httpsScans.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = httpsScanInfo.httpsScans.map((httpsScan) => { - httpsScan.domainId = domainId - return { - cursor: toGlobalId('https', httpsScan._key), - node: httpsScan, - } - }) - - return { - edges, - totalCount: httpsScanInfo.totalCount, - pageInfo: { - hasNextPage: httpsScanInfo.hasNextPage, - hasPreviousPage: httpsScanInfo.hasPreviousPage, - startCursor: toGlobalId('https', httpsScanInfo.startKey), - endCursor: toGlobalId('https', httpsScanInfo.endKey), - }, - } - } diff --git a/api-js/src/web-scan/loaders/load-ssl-by-key.js b/api-js/src/web-scan/loaders/load-ssl-by-key.js deleted file mode 100644 index 99e4197fa0..0000000000 --- a/api-js/src/web-scan/loaders/load-ssl-by-key.js +++ /dev/null @@ -1,34 +0,0 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' - -export const loadSslByKey = ({ query, userKey, i18n }) => - new DataLoader(async (keys) => { - let cursor - try { - cursor = await query` - WITH ssl - FOR sslScan IN ssl - FILTER sslScan._key IN ${keys} - RETURN MERGE({ id: sslScan._key, _type: "ssl" }, sslScan) - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadSslByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to find SSL scan(s). Please try again.`)) - } - - const sslMap = {} - try { - await cursor.forEach((sslScan) => { - sslMap[sslScan._key] = sslScan - }) - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadSslByKey: ${err}`, - ) - throw new Error(i18n._(t`Unable to find SSL scan(s). Please try again.`)) - } - - return keys.map((key) => sslMap[key]) - }) diff --git a/api-js/src/web-scan/loaders/load-ssl-connections-by-domain-id.js b/api-js/src/web-scan/loaders/load-ssl-connections-by-domain-id.js deleted file mode 100644 index ae88fc8185..0000000000 --- a/api-js/src/web-scan/loaders/load-ssl-connections-by-domain-id.js +++ /dev/null @@ -1,424 +0,0 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' - -export const loadSslConnectionByDomainId = - ({ query, userKey, cleanseInput, i18n }) => - async ({ - domainId, - startDate, - endDate, - after, - before, - first, - last, - orderBy, - }) => { - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(sslScan._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(ssl, ${afterId})` - - let documentField = aql`` - let sslField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'acceptable-ciphers') { - documentField = aql`afterVar.acceptable_ciphers` - sslField = aql`sslScan.acceptable_ciphers` - } else if (orderBy.field === 'acceptable-curves') { - documentField = aql`afterVar.acceptable_curves` - sslField = aql`sslScan.acceptable_curves` - } else if (orderBy.field === 'ccs-injection-vulnerable') { - documentField = aql`afterVar.ccs_injection_vulnerable` - sslField = aql`sslScan.ccs_injection_vulnerable` - } else if (orderBy.field === 'heartbleed-vulnerable') { - documentField = aql`afterVar.heartbleed_vulnerable` - sslField = aql`sslScan.heartbleed_vulnerable` - } else if (orderBy.field === 'strong-ciphers') { - documentField = aql`afterVar.strong_ciphers` - sslField = aql`sslScan.strong_ciphers` - } else if (orderBy.field === 'strong-curves') { - documentField = aql`afterVar.strong_curves` - sslField = aql`sslScan.strong_curves` - } else if (orderBy.field === 'supports-ecdh-key-exchange') { - documentField = aql`afterVar.supports_ecdh_key_exchange` - sslField = aql`sslScan.supports_ecdh_key_exchange` - } else if (orderBy.field === 'timestamp') { - documentField = aql`afterVar.timestamp` - sslField = aql`sslScan.timestamp` - } else if (orderBy.field === 'weak-ciphers') { - documentField = aql`afterVar.weak_ciphers` - sslField = aql`sslScan.weak_ciphers` - } else if (orderBy.field === 'weak-curves') { - documentField = aql`afterVar.weak_curves` - sslField = aql`sslScan.weak_curves` - } - - afterTemplate = aql` - FILTER ${sslField} ${afterTemplateDirection} ${documentField} - OR (${sslField} == ${documentField} - AND TO_NUMBER(sslScan._key) > TO_NUMBER(${afterId})) - ` - } - } - - let beforeTemplate = aql`` - let beforeVar = aql`` - - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(sslScan._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(ssl, ${beforeId})` - - let documentField = aql`` - let sslField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'acceptable-ciphers') { - documentField = aql`beforeVar.acceptable_ciphers` - sslField = aql`sslScan.acceptable_ciphers` - } else if (orderBy.field === 'acceptable-curves') { - documentField = aql`beforeVar.acceptable_curves` - sslField = aql`sslScan.acceptable_curves` - } else if (orderBy.field === 'ccs-injection-vulnerable') { - documentField = aql`beforeVar.ccs_injection_vulnerable` - sslField = aql`sslScan.ccs_injection_vulnerable` - } else if (orderBy.field === 'heartbleed-vulnerable') { - documentField = aql`beforeVar.heartbleed_vulnerable` - sslField = aql`sslScan.heartbleed_vulnerable` - } else if (orderBy.field === 'strong-ciphers') { - documentField = aql`beforeVar.strong_ciphers` - sslField = aql`sslScan.strong_ciphers` - } else if (orderBy.field === 'strong-curves') { - documentField = aql`beforeVar.strong_curves` - sslField = aql`sslScan.strong_curves` - } else if (orderBy.field === 'supports-ecdh-key-exchange') { - documentField = aql`beforeVar.supports_ecdh_key_exchange` - sslField = aql`sslScan.supports_ecdh_key_exchange` - } else if (orderBy.field === 'timestamp') { - documentField = aql`beforeVar.timestamp` - sslField = aql`sslScan.timestamp` - } else if (orderBy.field === 'weak-ciphers') { - documentField = aql`beforeVar.weak_ciphers` - sslField = aql`sslScan.weak_ciphers` - } else if (orderBy.field === 'weak-curves') { - documentField = aql`beforeVar.weak_curves` - sslField = aql`sslScan.weak_curves` - } - - beforeTemplate = aql` - FILTER ${sslField} ${beforeTemplateDirection} ${documentField} - OR (${sslField} == ${documentField} - AND TO_NUMBER(sslScan._key) < TO_NUMBER(${beforeId})) - ` - } - } - - let startDateTemplate = aql`` - if (typeof startDate !== 'undefined') { - startDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(sslScan.timestamp), - "%y-%m-%d" - ) >= - DATE_FORMAT( - DATE_TIMESTAMP(${startDate}), - "%y-%m-%d" - ) - ` - } - - let endDateTemplate = aql`` - if (typeof endDate !== 'undefined') { - endDateTemplate = aql` - FILTER DATE_FORMAT( - DATE_TIMESTAMP(sslScan.timestamp), - "%y-%m-%d" - ) <= - DATE_FORMAT( - DATE_TIMESTAMP(${endDate}), - "%y-%m-%d" - ) - ` - } - - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSslConnectionByDomainId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`SSL\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} tried to have \`first\` and \`last\` arguments set for: loadSslConnectionByDomainId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`SSL\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSslConnectionByDomainId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`SSL\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSslConnectionByDomainId.`, - ) - throw new Error( - i18n._( - t`Requesting ${amount} records on the \`SSL\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(sslScan._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(sslScan._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadSslConnectionByDomainId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(sslScan._key) > TO_NUMBER(LAST(retrievedSsl)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(sslScan._key) < TO_NUMBER(FIRST(retrievedSsl)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } - - let sslField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'acceptable-ciphers') { - sslField = aql`sslScan.acceptable_ciphers` - hasNextPageDocument = aql`LAST(retrievedSsl).acceptable_ciphers` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).acceptable_ciphers` - } else if (orderBy.field === 'acceptable-curves') { - sslField = aql`sslScan.acceptable_curves` - hasNextPageDocument = aql`LAST(retrievedSsl).acceptable_curves` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).acceptable_curves` - } else if (orderBy.field === 'ccs-injection-vulnerable') { - sslField = aql`sslScan.ccs_injection_vulnerable` - hasNextPageDocument = aql`LAST(retrievedSsl).ccs_injection_vulnerable` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).ccs_injection_vulnerable` - } else if (orderBy.field === 'heartbleed-vulnerable') { - sslField = aql`sslScan.heartbleed_vulnerable` - hasNextPageDocument = aql`LAST(retrievedSsl).heartbleed_vulnerable` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).heartbleed_vulnerable` - } else if (orderBy.field === 'strong-ciphers') { - sslField = aql`sslScan.strong_ciphers` - hasNextPageDocument = aql`LAST(retrievedSsl).strong_ciphers` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).strong_ciphers` - } else if (orderBy.field === 'strong-curves') { - sslField = aql`sslScan.strong_curves` - hasNextPageDocument = aql`LAST(retrievedSsl).strong_curves` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).strong_curves` - } else if (orderBy.field === 'supports-ecdh-key-exchange') { - sslField = aql`sslScan.supports_ecdh_key_exchange` - hasNextPageDocument = aql`LAST(retrievedSsl).supports_ecdh_key_exchange` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).supports_ecdh_key_exchange` - } else if (orderBy.field === 'timestamp') { - sslField = aql`sslScan.timestamp` - hasNextPageDocument = aql`LAST(retrievedSsl).timestamp` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).timestamp` - } else if (orderBy.field === 'weak-ciphers') { - sslField = aql`sslScan.weak_ciphers` - hasNextPageDocument = aql`LAST(retrievedSsl).weak_ciphers` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).weak_ciphers` - } else if (orderBy.field === 'weak-curves') { - sslField = aql`sslScan.weak_curves` - hasNextPageDocument = aql`LAST(retrievedSsl).weak_curves` - hasPreviousPageDocument = aql`FIRST(retrievedSsl).weak_curves` - } - - hasNextPageFilter = aql` - FILTER ${sslField} ${hasNextPageDirection} ${hasNextPageDocument} - OR (${sslField} == ${hasNextPageDocument} - AND TO_NUMBER(sslScan._key) > TO_NUMBER(LAST(retrievedSsl)._key)) - ` - - hasPreviousPageFilter = aql` - FILTER ${sslField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} - OR (${sslField} == ${hasPreviousPageDocument} - AND TO_NUMBER(sslScan._key) < TO_NUMBER(FIRST(retrievedSsl)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'acceptable-ciphers') { - sortByField = aql`sslScan.acceptable_ciphers ${orderBy.direction},` - } else if (orderBy.field === 'acceptable-curves') { - sortByField = aql`sslScan.acceptable_curves ${orderBy.direction},` - } else if (orderBy.field === 'ccs-injection-vulnerable') { - sortByField = aql`sslScan.ccs_injection_vulnerable ${orderBy.direction},` - } else if (orderBy.field === 'heartbleed-vulnerable') { - sortByField = aql`sslScan.heartbleed_vulnerable ${orderBy.direction},` - } else if (orderBy.field === 'strong-ciphers') { - sortByField = aql`sslScan.strong_ciphers ${orderBy.direction},` - } else if (orderBy.field === 'strong-curves') { - sortByField = aql`sslScan.strong_curves ${orderBy.direction},` - } else if (orderBy.field === 'supports-ecdh-key-exchange') { - sortByField = aql`sslScan.supports_ecdh_key_exchange ${orderBy.direction},` - } else if (orderBy.field === 'timestamp') { - sortByField = aql`sslScan.timestamp ${orderBy.direction},` - } else if (orderBy.field === 'weak-ciphers') { - sortByField = aql`sslScan.weak_ciphers ${orderBy.direction},` - } else if (orderBy.field === 'weak-curves') { - sortByField = aql`sslScan.weak_curves ${orderBy.direction},` - } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let requestedSslInfo - try { - requestedSslInfo = await query` - WITH domains, domainsSSL, ssl - LET sslKeys = ( - FOR v, e IN 1 OUTBOUND ${domainId} domainsSSL - OPTIONS {bfs: true} - RETURN v._key - ) - - ${afterVar} - ${beforeVar} - - LET retrievedSsl = ( - FOR sslScan IN ssl - FILTER sslScan._key IN sslKeys - ${afterTemplate} - ${beforeTemplate} - ${startDateTemplate} - ${endDateTemplate} - SORT - ${sortByField} - ${limitTemplate} - RETURN MERGE({ id: sslScan._key, _type: "ssl" }, sslScan) - ) - - LET hasNextPage = (LENGTH( - FOR sslScan IN ssl - FILTER sslScan._key IN sslKeys - ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(sslScan._key) ${sortString} LIMIT 1 - RETURN sslScan - ) > 0 ? true : false) - - LET hasPreviousPage = (LENGTH( - FOR sslScan IN ssl - FILTER sslScan._key IN sslKeys - ${hasPreviousPageFilter} - SORT ${sortByField} TO_NUMBER(sslScan._key) ${sortString} LIMIT 1 - RETURN sslScan - ) > 0 ? true : false) - - RETURN { - "sslScans": retrievedSsl, - "totalCount": LENGTH(sslKeys), - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedSsl)._key, - "endKey": LAST(retrievedSsl)._key - } - ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to get ssl information for ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load SSL scan(s). Please try again.`)) - } - - let sslScansInfo - try { - sslScansInfo = await requestedSslInfo.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to get ssl information for ${domainId}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to load SSL scan(s). Please try again.`)) - } - - if (sslScansInfo.sslScans.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } - - const edges = await sslScansInfo.sslScans.map((sslScan) => { - sslScan.domainId = domainId - return { - cursor: toGlobalId('ssl', sslScan._key), - node: sslScan, - } - }) - - return { - edges, - totalCount: sslScansInfo.totalCount, - pageInfo: { - hasNextPage: sslScansInfo.hasNextPage, - hasPreviousPage: sslScansInfo.hasPreviousPage, - startCursor: toGlobalId('ssl', sslScansInfo.startKey), - endCursor: toGlobalId('ssl', sslScansInfo.endKey), - }, - } - } diff --git a/api-js/src/web-scan/objects/__tests__/https-connection.test.js b/api-js/src/web-scan/objects/__tests__/https-connection.test.js deleted file mode 100644 index 3bf41b68c3..0000000000 --- a/api-js/src/web-scan/objects/__tests__/https-connection.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { httpsConnection } from '../index' - -describe('given the https connection object', () => { - describe('testing its field definitions', () => { - it('has a totalCount field', () => { - const demoType = httpsConnection.connectionType.getFields() - - expect(demoType).toHaveProperty('totalCount') - expect(demoType.totalCount.type).toMatchObject(GraphQLInt) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the totalCount resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsConnection.connectionType.getFields() - - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/__tests__/https-sub.test.js b/api-js/src/web-scan/objects/__tests__/https-sub.test.js deleted file mode 100644 index d96d119789..0000000000 --- a/api-js/src/web-scan/objects/__tests__/https-sub.test.js +++ /dev/null @@ -1,433 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList, GraphQLID } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { databaseOptions } from '../../../../database-options' -import { loadHttpsGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { guidanceTagType } from '../../../guidance-tag/objects' -import { httpsSubType } from '../index' -import { domainType } from '../../../domain/objects' -import { StatusEnum } from '../../../enums' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the httpsSubType object', () => { - describe('testing its field definitions', () => { - it('has sharedId field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('sharedId') - expect(demoType.sharedId.type).toMatchObject(GraphQLID) - }) - it('has a domain field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a status field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(StatusEnum) - }) - it('has implementation field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('implementation') - expect(demoType.implementation.type).toMatchObject(GraphQLString) - }) - it('has enforced field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('enforced') - expect(demoType.enforced.type).toMatchObject(GraphQLString) - }) - it('has hsts field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('hsts') - expect(demoType.hsts.type).toMatchObject(GraphQLString) - }) - it('has hstsAge field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('hstsAge') - expect(demoType.hstsAge.type).toMatchObject(GraphQLString) - }) - it('has preloaded field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('preloaded') - expect(demoType.preloaded.type).toMatchObject(GraphQLString) - }) - it('has a rawJson field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has negativeGuidanceTags field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has neutralGuidanceTags field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has positiveGuidanceTags field', () => { - const demoType = httpsSubType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the sharedId resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect(demoType.sharedId.resolve({ sharedId: 'sharedId' })).toEqual( - 'sharedId', - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = httpsSubType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainKey: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the status resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the implementation resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect( - demoType.implementation.resolve({ implementation: 'implementation' }), - ).toEqual('implementation') - }) - }) - describe('testing the enforced resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect(demoType.enforced.resolve({ enforced: 'enforced' })).toEqual( - 'enforced', - ) - }) - }) - describe('testing the hsts resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect(demoType.hsts.resolve({ hsts: 'hsts' })).toEqual('hsts') - }) - }) - describe('testing the hstsAge resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect(demoType.hstsAge.resolve({ hstsAge: 'hstsAge' })).toEqual( - 'hstsAge', - ) - }) - }) - describe('testing the preloaded resolver', () => { - it('returns the parsed value', () => { - const demoType = httpsSubType.getFields() - - expect(demoType.preloaded.resolve({ preloaded: 'preloaded' })).toEqual( - 'preloaded', - ) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsSubType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - let query, drop, truncate, collections, httpsGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - httpsGT = await collections.httpsGuidanceTags.save({ - _key: 'https1', - en: { - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = httpsSubType.getFields() - - const loader = loadHttpsGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const negativeTags = ['https1'] - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - {}, - { loaders: { loadHttpsGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: httpsGT._id, - _key: httpsGT._key, - _rev: httpsGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - ]) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - let query, drop, truncate, collections, httpsGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - httpsGT = await collections.httpsGuidanceTags.save({ - _key: 'https1', - en: { - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = httpsSubType.getFields() - - const loader = loadHttpsGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const neutralTags = ['https1'] - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - {}, - { loaders: { loadHttpsGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: httpsGT._id, - _key: httpsGT._key, - _rev: httpsGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - ]) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - let query, drop, truncate, collections, httpsGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - httpsGT = await collections.httpsGuidanceTags.save({ - _key: 'https1', - en: { - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = httpsSubType.getFields() - - const loader = loadHttpsGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const positiveTags = ['https1'] - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - {}, - { loaders: { loadHttpsGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: httpsGT._id, - _key: httpsGT._key, - _rev: httpsGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - ]) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/__tests__/https.test.js b/api-js/src/web-scan/objects/__tests__/https.test.js deleted file mode 100644 index 9fa1cbc3d2..0000000000 --- a/api-js/src/web-scan/objects/__tests__/https.test.js +++ /dev/null @@ -1,429 +0,0 @@ -import { GraphQLID, GraphQLNonNull, GraphQLString } from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { GraphQLDate, GraphQLJSON } from 'graphql-scalars' - -import { domainType } from '../../../domain/objects' -import { guidanceTagConnection } from '../../../guidance-tag/objects' -import { httpsType } from '../index' - -describe('given the https gql object', () => { - describe('testing the field definitions', () => { - it('has an id field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a domain field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a timestamp field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('timestamp') - expect(demoType.timestamp.type).toMatchObject(GraphQLDate) - }) - it('has a implementation field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('implementation') - expect(demoType.implementation.type).toMatchObject(GraphQLString) - }) - it('has an enforced field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('enforced') - expect(demoType.enforced.type).toMatchObject(GraphQLString) - }) - it('has a hsts field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('hsts') - expect(demoType.hsts.type).toMatchObject(GraphQLString) - }) - it('has a hstsAge field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('hstsAge') - expect(demoType.hstsAge.type).toMatchObject(GraphQLString) - }) - it('has a preloaded field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('preloaded') - expect(demoType.preloaded.type).toMatchObject(GraphQLString) - }) - it('has a rawJson field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has a guidanceTags field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('guidanceTags') - expect(demoType.guidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a negativeGuidanceTags field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a neutralGuidanceTags field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a positiveGuidanceTags field', () => { - const demoType = httpsType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - }) - describe('testing the field resolvers', () => { - describe('testing the id resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('https', '1'), - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = httpsType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainId: 'domains/1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the timestamp resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect( - demoType.timestamp.resolve({ timestamp: '2020-10-02T12:43:39Z' }), - ).toEqual(new Date('2020-10-02T12:43:39.000Z')) - }) - }) - describe('testing the implementation resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect( - demoType.implementation.resolve({ implementation: 'implementation' }), - ).toEqual('implementation') - }) - }) - describe('testing the enforced resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect(demoType.enforced.resolve({ enforced: 'enforced' })).toEqual( - 'enforced', - ) - }) - }) - describe('testing the hsts resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect(demoType.hsts.resolve({ hsts: 'hsts' })).toEqual('hsts') - }) - }) - describe('testing the hstsAge resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect(demoType.hstsAge.resolve({ hstsAge: 'hstsAge' })).toEqual( - 'hstsAge', - ) - }) - }) - describe('testing the preloaded resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - expect(demoType.preloaded.resolve({ preloaded: 'preloaded' })).toEqual( - 'preloaded', - ) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = httpsType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the guidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = httpsType.getFields() - const guidanceTags = ['https1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'https1'), - node: { - _id: 'httpsGuidanceTags/https1', - _key: 'https1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'https1'), - endCursor: toGlobalId('guidanceTags', 'https1'), - }, - } - - expect( - await demoType.guidanceTags.resolve( - { guidanceTags }, - { first: 1 }, - { - loaders: { - loadHttpsGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = httpsType.getFields() - const negativeTags = ['https1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'https1'), - node: { - _id: 'httpsGuidanceTags/https1', - _key: 'https1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'https1'), - endCursor: toGlobalId('guidanceTags', 'https1'), - }, - } - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - { first: 1 }, - { - loaders: { - loadHttpsGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = httpsType.getFields() - const neutralTags = ['https1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'https1'), - node: { - _id: 'httpsGuidanceTags/https1', - _key: 'https1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'https1'), - endCursor: toGlobalId('guidanceTags', 'https1'), - }, - } - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - { first: 1 }, - { - loaders: { - loadHttpsGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = httpsType.getFields() - const positiveTags = ['https1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'https1'), - node: { - _id: 'httpsGuidanceTags/https1', - _key: 'https1', - _rev: 'rev', - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'https1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'https1', - tagName: 'HTTPS-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'https1'), - endCursor: toGlobalId('guidanceTags', 'https1'), - }, - } - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - { first: 1 }, - { - loaders: { - loadHttpsGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/__tests__/ssl-connection.test.js b/api-js/src/web-scan/objects/__tests__/ssl-connection.test.js deleted file mode 100644 index f5c42fa624..0000000000 --- a/api-js/src/web-scan/objects/__tests__/ssl-connection.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { sslConnection } from '../index' - -describe('given the ssl connection object', () => { - describe('testing its field definitions', () => { - it('has a totalCount field', () => { - const demoType = sslConnection.connectionType.getFields() - - expect(demoType).toHaveProperty('totalCount') - expect(demoType.totalCount.type).toMatchObject(GraphQLInt) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the totalCount resolver', () => { - it('returns the resolved value', () => { - const demoType = sslConnection.connectionType.getFields() - - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/__tests__/ssl-sub.test.js b/api-js/src/web-scan/objects/__tests__/ssl-sub.test.js deleted file mode 100644 index 208caf8f71..0000000000 --- a/api-js/src/web-scan/objects/__tests__/ssl-sub.test.js +++ /dev/null @@ -1,543 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLBoolean, GraphQLID, GraphQLList, GraphQLString } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { databaseOptions } from '../../../../database-options' -import { loadSslGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { guidanceTagType } from '../../../guidance-tag/objects' -import { sslSubType } from '../index' -import { domainType } from '../../../domain/objects' -import { StatusEnum } from '../../../enums' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the sslSubType object', () => { - describe('testing field definitions', () => { - it('has sharedId field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('sharedId') - expect(demoType.sharedId.type).toMatchObject(GraphQLID) - }) - it('has a domain field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a status field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('status') - expect(demoType.status.type).toMatchObject(StatusEnum) - }) - it('has a acceptableCiphers field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('acceptableCiphers') - expect(demoType.acceptableCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a acceptableCurves field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('acceptableCiphers') - expect(demoType.acceptableCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a ccsInjectionVulnerable field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('ccsInjectionVulnerable') - expect(demoType.ccsInjectionVulnerable.type).toMatchObject(GraphQLBoolean) - }) - it('has a heartbleedVulnerable field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('heartbleedVulnerable') - expect(demoType.heartbleedVulnerable.type).toMatchObject(GraphQLBoolean) - }) - it('has a strongCiphers field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('strongCiphers') - expect(demoType.strongCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a strongCurves field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('strongCurves') - expect(demoType.strongCurves.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a supportsEcdhKeyExchange field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('supportsEcdhKeyExchange') - expect(demoType.supportsEcdhKeyExchange.type).toMatchObject( - GraphQLBoolean, - ) - }) - it('has a weakCiphers field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('weakCiphers') - expect(demoType.weakCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a weakCurves field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('weakCurves') - expect(demoType.weakCurves.type).toMatchObject(GraphQLList(GraphQLString)) - }) - it('has a rawJson field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has negativeGuidanceTags field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has neutralGuidanceTags field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - it('has positiveGuidanceTags field', () => { - const demoType = sslSubType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - GraphQLList(guidanceTagType), - ) - }) - }) - describe('testing its field resolvers', () => { - describe('testing the sharedId resolver', () => { - it('returns the parsed value', () => { - const demoType = sslSubType.getFields() - - expect(demoType.sharedId.resolve({ sharedId: 'sharedId' })).toEqual( - 'sharedId', - ) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = sslSubType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainKey: '1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the status resolver', () => { - it('returns the parsed value', () => { - const demoType = sslSubType.getFields() - - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') - }) - }) - describe('testing the acceptableCiphers resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const ciphers = [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ] - - expect( - demoType.acceptableCiphers.resolve({ acceptable_ciphers: ciphers }), - ).toEqual([ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ]) - }) - }) - describe('testing the acceptableCurves resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const curves = ['curve123'] - - expect( - demoType.acceptableCurves.resolve({ acceptable_curves: curves }), - ).toEqual(['curve123']) - }) - }) - describe('testing the ccsInjectionVulnerable resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - expect( - demoType.ccsInjectionVulnerable.resolve({ - ccs_injection_vulnerable: false, - }), - ).toEqual(false) - }) - }) - describe('testing the heartbleedVulnerable resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - expect( - demoType.heartbleedVulnerable.resolve({ - heartbleed_vulnerable: false, - }), - ).toEqual(false) - }) - }) - describe('testing the strongCiphers resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const ciphers = [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ] - - expect( - demoType.strongCiphers.resolve({ strong_ciphers: ciphers }), - ).toEqual([ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ]) - }) - }) - describe('testing the strongCurves resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const curves = ['curve123'] - - expect( - demoType.strongCurves.resolve({ strong_curves: curves }), - ).toEqual(['curve123']) - }) - }) - describe('testing the supportsEcdhKeyExchange resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - expect( - demoType.supportsEcdhKeyExchange.resolve({ - supports_ecdh_key_exchange: false, - }), - ).toEqual(false) - }) - }) - describe('testing the weakCiphers resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const ciphers = [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ] - - expect(demoType.weakCiphers.resolve({ weak_ciphers: ciphers })).toEqual( - [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - ) - }) - }) - describe('testing the weakCurves resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const curves = ['curve123'] - - expect(demoType.weakCurves.resolve({ weak_curves: curves })).toEqual([ - 'curve123', - ]) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = sslSubType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - let query, drop, truncate, collections, sslGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - sslGT = await collections.sslGuidanceTags.save({ - _key: 'ssl1', - en: { - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = sslSubType.getFields() - - const loader = loadSslGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const negativeTags = ['ssl1'] - - expect( - await demoType.negativeGuidanceTags.resolve( - { negativeTags }, - {}, - { loaders: { loadSslGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: sslGT._id, - _key: sslGT._key, - _rev: sslGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'ssl1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - ]) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - let query, drop, truncate, collections, sslGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - sslGT = await collections.sslGuidanceTags.save({ - _key: 'ssl1', - en: { - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = sslSubType.getFields() - - const loader = loadSslGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const neutralTags = ['ssl1'] - - expect( - await demoType.neutralGuidanceTags.resolve( - { neutralTags }, - {}, - { loaders: { loadSslGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: sslGT._id, - _key: sslGT._key, - _rev: sslGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'ssl1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - ]) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - let query, drop, truncate, collections, sslGT - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - beforeEach(async () => { - await truncate() - sslGT = await collections.sslGuidanceTags.save({ - _key: 'ssl1', - en: { - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - }) - afterAll(async () => { - await drop() - }) - it('returns the parsed value', async () => { - const demoType = sslSubType.getFields() - - const loader = loadSslGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }) - const positiveTags = ['ssl1'] - - expect( - await demoType.positiveGuidanceTags.resolve( - { positiveTags }, - {}, - { loaders: { loadSslGuidanceTagByTagId: loader } }, - ), - ).toEqual([ - { - _id: sslGT._id, - _key: sslGT._key, - _rev: sslGT._rev, - _type: 'guidanceTag', - guidance: 'Some Interesting Guidance', - id: 'ssl1', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - ]) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/__tests__/ssl.test.js b/api-js/src/web-scan/objects/__tests__/ssl.test.js deleted file mode 100644 index 225935bfac..0000000000 --- a/api-js/src/web-scan/objects/__tests__/ssl.test.js +++ /dev/null @@ -1,544 +0,0 @@ -import { - GraphQLNonNull, - GraphQLID, - GraphQLString, - GraphQLList, - GraphQLBoolean, -} from 'graphql' -import { toGlobalId } from 'graphql-relay' -import { GraphQLJSON, GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../../domain/objects' -import { guidanceTagConnection } from '../../../guidance-tag/objects' -import { sslType } from '../index' - -describe('given the ssl gql object', () => { - describe('testing field definitions', () => { - it('has an id field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) - }) - it('has a acceptableCiphers field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('acceptableCiphers') - expect(demoType.acceptableCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a acceptableCurves field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('acceptableCiphers') - expect(demoType.acceptableCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a ccsInjectionVulnerable field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('ccsInjectionVulnerable') - expect(demoType.ccsInjectionVulnerable.type).toMatchObject(GraphQLBoolean) - }) - it('has a domain field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a heartbleedVulnerable field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('heartbleedVulnerable') - expect(demoType.heartbleedVulnerable.type).toMatchObject(GraphQLBoolean) - }) - it('has a rawJson field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('rawJson') - expect(demoType.rawJson.type).toEqual(GraphQLJSON) - }) - it('has a strongCiphers field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('strongCiphers') - expect(demoType.strongCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a strongCurves field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('strongCurves') - expect(demoType.strongCurves.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a supportsEcdhKeyExchange field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('supportsEcdhKeyExchange') - expect(demoType.supportsEcdhKeyExchange.type).toMatchObject( - GraphQLBoolean, - ) - }) - it('has a timestamp field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('timestamp') - expect(demoType.timestamp.type).toMatchObject(GraphQLDate) - }) - it('has a weakCiphers field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('weakCiphers') - expect(demoType.weakCiphers.type).toMatchObject( - GraphQLList(GraphQLString), - ) - }) - it('has a weakCurves field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('weakCurves') - expect(demoType.weakCurves.type).toMatchObject(GraphQLList(GraphQLString)) - }) - it('has a guidanceTags field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('guidanceTags') - expect(demoType.guidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a negativeGuidanceTags field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('negativeGuidanceTags') - expect(demoType.negativeGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a neutralGuidanceTags field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('neutralGuidanceTags') - expect(demoType.neutralGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - it('has a positiveGuidanceTags field', () => { - const demoType = sslType.getFields() - - expect(demoType).toHaveProperty('positiveGuidanceTags') - expect(demoType.positiveGuidanceTags.type).toMatchObject( - guidanceTagConnection.connectionType, - ) - }) - }) - - describe('testing the field resolvers', () => { - describe('testing the id resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('ssl', '1')) - }) - }) - describe('testing the acceptableCiphers resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const ciphers = [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ] - - expect( - demoType.acceptableCiphers.resolve({ acceptable_ciphers: ciphers }), - ).toEqual([ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ]) - }) - }) - describe('testing the acceptableCurves resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const curves = ['curve123'] - - expect( - demoType.acceptableCurves.resolve({ acceptable_curves: curves }), - ).toEqual(['curve123']) - }) - }) - describe('testing the ccsInjectionVulnerable resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - expect( - demoType.ccsInjectionVulnerable.resolve({ - ccs_injection_vulnerable: false, - }), - ).toEqual(false) - }) - }) - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = sslType.getFields() - - const expectedResult = { - _id: 'domains/1', - _key: '1', - _rev: 'rev', - _type: 'domain', - id: '1', - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { domainId: 'domains/1' }, - {}, - { - loaders: { - loadDomainByKey: { - load: jest.fn().mockReturnValue(expectedResult), - }, - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the heartbleedVulnerable resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - expect( - demoType.heartbleedVulnerable.resolve({ - heartbleed_vulnerable: false, - }), - ).toEqual(false) - }) - }) - describe('testing the rawJSON resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const rawJson = { item: 1234 } - - expect(demoType.rawJson.resolve({ rawJson })).toEqual( - JSON.stringify(rawJson), - ) - }) - }) - describe('testing the strongCiphers resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const ciphers = [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ] - - expect( - demoType.strongCiphers.resolve({ strong_ciphers: ciphers }), - ).toEqual([ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ]) - }) - }) - describe('testing the strongCurves resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const curves = ['curve123'] - - expect( - demoType.strongCurves.resolve({ strong_curves: curves }), - ).toEqual(['curve123']) - }) - }) - describe('testing the supportsEcdhKeyExchange resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - expect( - demoType.supportsEcdhKeyExchange.resolve({ - supports_ecdh_key_exchange: false, - }), - ).toEqual(false) - }) - }) - describe('testing the timestamp resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - expect( - demoType.timestamp.resolve({ timestamp: '2020-10-02T12:43:39Z' }), - ).toEqual(new Date('2020-10-02T12:43:39Z')) - }) - }) - describe('testing the weakCiphers resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const ciphers = [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ] - - expect(demoType.weakCiphers.resolve({ weak_ciphers: ciphers })).toEqual( - [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - ) - }) - }) - describe('testing the weakCurves resolver', () => { - it('returns the resolved value', () => { - const demoType = sslType.getFields() - - const curves = ['curve123'] - - expect(demoType.weakCurves.resolve({ weak_curves: curves })).toEqual([ - 'curve123', - ]) - }) - }) - describe('testing the guidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = sslType.getFields() - const guidanceTags = ['ssl1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'ssl1'), - node: { - _id: 'sslGuidanceTags/ssl1', - _key: 'ssl1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'ssl1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'ssl1'), - endCursor: toGlobalId('guidanceTags', 'ssl1'), - }, - } - - await expect( - demoType.guidanceTags.resolve( - { guidanceTags }, - { first: 1 }, - { - loaders: { - loadSslGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the negativeGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = sslType.getFields() - const negativeTags = ['ssl1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'ssl1'), - node: { - _id: 'sslGuidanceTags/ssl1', - _key: 'ssl1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'ssl1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'ssl1'), - endCursor: toGlobalId('guidanceTags', 'ssl1'), - }, - } - - await expect( - demoType.negativeGuidanceTags.resolve( - { negativeTags }, - { first: 1 }, - { - loaders: { - loadSslGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the neutralGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = sslType.getFields() - const neutralTags = ['ssl1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'ssl1'), - node: { - _id: 'sslGuidanceTags/ssl1', - _key: 'ssl1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'ssl1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'ssl1'), - endCursor: toGlobalId('guidanceTags', 'ssl1'), - }, - } - - await expect( - demoType.neutralGuidanceTags.resolve( - { neutralTags }, - { first: 1 }, - { - loaders: { - loadSslGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the positiveGuidanceTags resolver', () => { - it('returns the resolved value', async () => { - const demoType = sslType.getFields() - const positiveTags = ['ssl1'] - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('guidanceTags', 'ssl1'), - node: { - _id: 'sslGuidanceTags/ssl1', - _key: 'ssl1', - _rev: 'rev', - _type: 'guidanceTag', - id: 'ssl1', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - tagId: 'ssl1', - tagName: 'SSL-TAG', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('guidanceTags', 'ssl1'), - endCursor: toGlobalId('guidanceTags', 'ssl1'), - }, - } - - await expect( - demoType.positiveGuidanceTags.resolve( - { positiveTags }, - { first: 1 }, - { - loaders: { - loadSslGuidanceTagConnectionsByTagId: jest - .fn() - .mockReturnValue(expectedResult), - }, - }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/__tests__/web-scan.test.js b/api-js/src/web-scan/objects/__tests__/web-scan.test.js deleted file mode 100644 index ae3858b3f7..0000000000 --- a/api-js/src/web-scan/objects/__tests__/web-scan.test.js +++ /dev/null @@ -1,209 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { cleanseInput } from '../../../validators' -import { loadDomainByKey } from '../../../domain/loaders' -import { domainType } from '../../../domain/objects' -import { - loadHttpsConnectionsByDomainId, - loadSslConnectionByDomainId, -} from '../../loaders' - -import { webScanType, httpsConnection, sslConnection } from '../index' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given the web scan gql object', () => { - describe('testing the field definitions', () => { - it('has a domain field', () => { - const demoType = webScanType.getFields() - - expect(demoType).toHaveProperty('domain') - expect(demoType.domain.type).toMatchObject(domainType) - }) - it('has a https field', () => { - const demoType = webScanType.getFields() - - expect(demoType).toHaveProperty('https') - expect(demoType.https.type).toMatchObject(httpsConnection.connectionType) - }) - it('has a ssl field', () => { - const demoType = webScanType.getFields() - - expect(demoType).toHaveProperty('ssl') - expect(demoType.ssl.type).toMatchObject(sslConnection.connectionType) - }) - }) - - describe('testing the field resolvers', () => { - let query, drop, truncate, collections, domain, https, ssl - - beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - }) - - beforeEach(async () => { - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - https = await collections.https.save({ - timestamp: '2020-10-02T12:43:39Z', - implementation: 'Valid HTTPS', - enforced: 'Strict', - hsts: 'HSTS Max Age Too Short', - hstsAge: '31622400', - preloaded: 'HSTS Preloaded', - guidanceTags: ['https1'], - }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, - }) - ssl = await collections.ssl.save({ - timestamp: '2020-10-02T12:43:39Z', - guidanceTags: ['ssl1'], - }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, - }) - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await drop() - }) - - describe('testing the domain resolver', () => { - it('returns the resolved value', async () => { - const demoType = webScanType.getFields() - - const loader = loadDomainByKey({ query, userKey: '1', i18n: {} }) - - const expectedResult = { - _id: domain._id, - _key: domain._key, - _rev: domain._rev, - _type: 'domain', - id: domain._key, - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - } - - await expect( - demoType.domain.resolve( - { _key: domain._key }, - {}, - { loaders: { loadDomainByKey: loader } }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the https resolver', () => { - it('returns the resolved value', async () => { - const demoType = webScanType.getFields() - - const loader = loadHttpsConnectionsByDomainId({ - query, - userKey: '1', - cleanseInput, - i18n: {}, - }) - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('https', https._key), - node: { - _id: https._id, - _key: https._key, - _rev: https._rev, - _type: 'https', - domainId: domain._id, - enforced: 'Strict', - guidanceTags: ['https1'], - hsts: 'HSTS Max Age Too Short', - hstsAge: '31622400', - id: https._key, - implementation: 'Valid HTTPS', - preloaded: 'HSTS Preloaded', - timestamp: '2020-10-02T12:43:39Z', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('https', https._key), - endCursor: toGlobalId('https', https._key), - }, - } - - await expect( - demoType.https.resolve( - { _id: domain._id }, - { first: 1 }, - { loaders: { loadHttpsConnectionsByDomainId: loader } }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - describe('testing the ssl resolver', () => { - it('returns the resolved value', async () => { - const demoType = webScanType.getFields() - - const loader = loadSslConnectionByDomainId({ - query, - userKey: '1', - cleanseInput, - i18n: {}, - }) - - const expectedResult = { - edges: [ - { - cursor: toGlobalId('ssl', ssl._key), - node: { - _id: ssl._id, - _key: ssl._key, - _rev: ssl._rev, - _type: 'ssl', - domainId: domain._id, - guidanceTags: ['ssl1'], - id: ssl._key, - timestamp: '2020-10-02T12:43:39Z', - }, - }, - ], - totalCount: 1, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: toGlobalId('ssl', ssl._key), - endCursor: toGlobalId('ssl', ssl._key), - }, - } - - await expect( - demoType.ssl.resolve( - { _id: domain._id }, - { first: 1 }, - { loaders: { loadSslConnectionByDomainId: loader } }, - ), - ).resolves.toEqual(expectedResult) - }) - }) - }) -}) diff --git a/api-js/src/web-scan/objects/https-connection.js b/api-js/src/web-scan/objects/https-connection.js deleted file mode 100644 index 0e8ae14ff1..0000000000 --- a/api-js/src/web-scan/objects/https-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { httpsType } from './https' - -export const httpsConnection = connectionDefinitions({ - name: 'HTTPS', - nodeType: httpsType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of https scans for a given domain.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/web-scan/objects/https-sub.js b/api-js/src/web-scan/objects/https-sub.js deleted file mode 100644 index fd8e1b3c41..0000000000 --- a/api-js/src/web-scan/objects/https-sub.js +++ /dev/null @@ -1,109 +0,0 @@ -import { - GraphQLObjectType, - GraphQLID, - GraphQLString, - GraphQLList, -} from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { guidanceTagType } from '../../guidance-tag/objects' -import { StatusEnum } from '../../enums' - -export const httpsSubType = new GraphQLObjectType({ - name: 'HttpsSub', - description: - 'HTTPS gql object containing the fields for the `dkimScanData` subscription.', - fields: () => ({ - sharedId: { - type: GraphQLID, - description: `The shared id to match scans together.`, - resolve: ({ sharedId }) => sharedId, - }, - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainKey }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(domainKey) - return domain - }, - }, - status: { - type: StatusEnum, - description: 'The success status of the scan.', - resolve: ({ status }) => status, - }, - implementation: { - type: GraphQLString, - description: `State of the HTTPS implementation on the server and any issues therein.`, - resolve: ({ implementation }) => implementation, - }, - enforced: { - type: GraphQLString, - description: `Degree to which HTTPS is enforced on the server based on behaviour.`, - resolve: ({ enforced }) => enforced, - }, - hsts: { - type: GraphQLString, - description: `Presence and completeness of HSTS implementation.`, - resolve: ({ hsts }) => hsts, - }, - hstsAge: { - type: GraphQLString, - description: `Denotes how long the domain should only be accessed using HTTPS`, - resolve: ({ hstsAge }) => hstsAge, - }, - preloaded: { - type: GraphQLString, - description: `Denotes whether the domain has been submitted and included within HSTS preload list.`, - resolve: ({ preloaded }) => preloaded, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - negativeGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Negative guidance tags found during scan.`, - resolve: async ( - { negativeTags }, - _args, - { loaders: { loadHttpsGuidanceTagByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagByTagId.loadMany( - negativeTags, - ) - return httpsTags - }, - }, - neutralGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Neutral guidance tags found during scan.`, - resolve: async ( - { neutralTags }, - _args, - { loaders: { loadHttpsGuidanceTagByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagByTagId.loadMany( - neutralTags, - ) - return httpsTags - }, - }, - positiveGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Positive guidance tags found during scan.`, - resolve: async ( - { positiveTags }, - _args, - { loaders: { loadHttpsGuidanceTagByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagByTagId.loadMany( - positiveTags, - ) - return httpsTags - }, - }, - }), -}) diff --git a/api-js/src/web-scan/objects/https.js b/api-js/src/web-scan/objects/https.js deleted file mode 100644 index bfa770f7af..0000000000 --- a/api-js/src/web-scan/objects/https.js +++ /dev/null @@ -1,152 +0,0 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLDate, GraphQLJSON } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { nodeInterface } from '../../node' -import { guidanceTagOrder } from '../../guidance-tag/inputs' -import { guidanceTagConnection } from '../../guidance-tag/objects' - -export const httpsType = new GraphQLObjectType({ - name: 'HTTPS', - fields: () => ({ - id: globalIdField('https'), - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainId }, _, { loaders: { loadDomainByKey } }) => { - const domainKey = domainId.split('/')[1] - const domain = await loadDomainByKey.load(domainKey) - domain.id = domain._key - return domain - }, - }, - timestamp: { - type: GraphQLDate, - description: `The time the scan was initiated.`, - resolve: ({ timestamp }) => new Date(timestamp), - }, - implementation: { - type: GraphQLString, - description: `State of the HTTPS implementation on the server and any issues therein.`, - resolve: ({ implementation }) => implementation, - }, - enforced: { - type: GraphQLString, - description: `Degree to which HTTPS is enforced on the server based on behaviour.`, - resolve: ({ enforced }) => enforced, - }, - hsts: { - type: GraphQLString, - description: `Presence and completeness of HSTS implementation.`, - resolve: ({ hsts }) => hsts, - }, - hstsAge: { - type: GraphQLString, - description: `Denotes how long the domain should only be accessed using HTTPS`, - resolve: ({ hstsAge }) => hstsAge, - }, - preloaded: { - type: GraphQLString, - description: `Denotes whether the domain has been submitted and included within HSTS preload list.`, - resolve: ({ preloaded }) => preloaded, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - guidanceTags: { - type: guidanceTagConnection.connectionType, - deprecationReason: - 'This has been sub-divided into neutral, negative, and positive tags.', - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Guidance tags found during scan.`, - resolve: async ( - { guidanceTags }, - args, - { loaders: { loadHttpsGuidanceTagConnectionsByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagConnectionsByTagId({ - httpsGuidanceTags: guidanceTags, - ...args, - }) - return httpsTags - }, - }, - negativeGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Negative guidance tags found during scan.`, - resolve: async ( - { negativeTags }, - args, - { loaders: { loadHttpsGuidanceTagConnectionsByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagConnectionsByTagId({ - httpsGuidanceTags: negativeTags, - ...args, - }) - return httpsTags - }, - }, - neutralGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Neutral guidance tags found during scan.`, - resolve: async ( - { neutralTags }, - args, - { loaders: { loadHttpsGuidanceTagConnectionsByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagConnectionsByTagId({ - httpsGuidanceTags: neutralTags, - ...args, - }) - return httpsTags - }, - }, - positiveGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Positive guidance tags found during scan.`, - resolve: async ( - { positiveTags }, - args, - { loaders: { loadHttpsGuidanceTagConnectionsByTagId } }, - ) => { - const httpsTags = await loadHttpsGuidanceTagConnectionsByTagId({ - httpsGuidanceTags: positiveTags, - ...args, - }) - return httpsTags - }, - }, - }), - interfaces: [nodeInterface], - description: `Hyper Text Transfer Protocol Secure scan results.`, -}) diff --git a/api-js/src/web-scan/objects/index.js b/api-js/src/web-scan/objects/index.js deleted file mode 100644 index 2653cead0d..0000000000 --- a/api-js/src/web-scan/objects/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export * from './https-sub' -export * from './https' -export * from './https-connection' -export * from './ssl-sub' -export * from './ssl' -export * from './ssl-connection' -export * from './web-scan' diff --git a/api-js/src/web-scan/objects/ssl-connection.js b/api-js/src/web-scan/objects/ssl-connection.js deleted file mode 100644 index a6f5580185..0000000000 --- a/api-js/src/web-scan/objects/ssl-connection.js +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - -import { sslType } from './ssl' - -export const sslConnection = connectionDefinitions({ - name: 'SSL', - nodeType: sslType, - connectionFields: () => ({ - totalCount: { - type: GraphQLInt, - description: 'The total amount of https scans for a given domain.', - resolve: ({ totalCount }) => totalCount, - }, - }), -}) diff --git a/api-js/src/web-scan/objects/ssl-sub.js b/api-js/src/web-scan/objects/ssl-sub.js deleted file mode 100644 index b2640d1ceb..0000000000 --- a/api-js/src/web-scan/objects/ssl-sub.js +++ /dev/null @@ -1,132 +0,0 @@ -import { - GraphQLObjectType, - GraphQLList, - GraphQLString, - GraphQLBoolean, - GraphQLID, -} from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { guidanceTagType } from '../../guidance-tag/objects' -import { StatusEnum } from '../../enums' - -export const sslSubType = new GraphQLObjectType({ - name: 'SslSub', - description: - 'SSL gql object containing the fields for the `dkimScanData` subscription.', - fields: () => ({ - sharedId: { - type: GraphQLID, - description: `The shared id to match scans together.`, - resolve: ({ sharedId }) => sharedId, - }, - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainKey }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(domainKey) - return domain - }, - }, - status: { - type: StatusEnum, - description: 'The success status of the scan.', - resolve: ({ status }) => status, - }, - acceptableCiphers: { - type: GraphQLList(GraphQLString), - description: - 'List of ciphers in use by the server deemed to be "acceptable".', - resolve: ({ acceptable_ciphers: acceptableCiphers }) => acceptableCiphers, - }, - acceptableCurves: { - type: GraphQLList(GraphQLString), - description: - 'List of curves in use by the server deemed to be "acceptable".', - resolve: ({ acceptable_curves: acceptableCurves }) => acceptableCurves, - }, - ccsInjectionVulnerable: { - type: GraphQLBoolean, - description: 'Denotes vulnerability to OpenSSL CCS Injection.', - resolve: ({ ccs_injection_vulnerable: ccsInjectionVulnerable }) => - ccsInjectionVulnerable, - }, - heartbleedVulnerable: { - type: GraphQLBoolean, - description: 'Denotes vulnerability to "Heartbleed" exploit.', - resolve: ({ heartbleed_vulnerable: heartbleedVulnerable }) => - heartbleedVulnerable, - }, - strongCiphers: { - type: GraphQLList(GraphQLString), - description: - 'List of ciphers in use by the server deemed to be "strong".', - resolve: ({ strong_ciphers: strongCiphers }) => strongCiphers, - }, - strongCurves: { - type: GraphQLList(GraphQLString), - description: 'List of curves in use by the server deemed to be "strong".', - resolve: ({ strong_curves: strongCurves }) => strongCurves, - }, - supportsEcdhKeyExchange: { - type: GraphQLBoolean, - description: 'Denotes support for elliptic curve key pairs.', - resolve: ({ supports_ecdh_key_exchange: supportsEcdhKeyExchange }) => - supportsEcdhKeyExchange, - }, - weakCiphers: { - type: GraphQLList(GraphQLString), - description: - 'List of ciphers in use by the server deemed to be "weak" or in other words, are not compliant with security standards.', - resolve: ({ weak_ciphers: weakCiphers }) => weakCiphers, - }, - weakCurves: { - type: GraphQLList(GraphQLString), - description: - 'List of curves in use by the server deemed to be "weak" or in other words, are not compliant with security standards.', - resolve: ({ weak_curves: weakCurves }) => weakCurves, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - negativeGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Negative guidance tags found during scan.`, - resolve: async ( - { negativeTags }, - _args, - { loaders: { loadSslGuidanceTagByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagByTagId.loadMany(negativeTags) - return sslTags - }, - }, - neutralGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Neutral guidance tags found during scan.`, - resolve: async ( - { neutralTags }, - _args, - { loaders: { loadSslGuidanceTagByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagByTagId.loadMany(neutralTags) - return sslTags - }, - }, - positiveGuidanceTags: { - type: GraphQLList(guidanceTagType), - description: `Positive guidance tags found during scan.`, - resolve: async ( - { positiveTags }, - _args, - { loaders: { loadSslGuidanceTagByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagByTagId.loadMany(positiveTags) - return sslTags - }, - }, - }), -}) diff --git a/api-js/src/web-scan/objects/ssl.js b/api-js/src/web-scan/objects/ssl.js deleted file mode 100644 index a18b2901df..0000000000 --- a/api-js/src/web-scan/objects/ssl.js +++ /dev/null @@ -1,185 +0,0 @@ -import { - GraphQLBoolean, - GraphQLList, - GraphQLObjectType, - GraphQLString, -} from 'graphql' -import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLJSON, GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { nodeInterface } from '../../node' -import { guidanceTagOrder } from '../../guidance-tag/inputs' -import { guidanceTagConnection } from '../../guidance-tag/objects' - -export const sslType = new GraphQLObjectType({ - name: 'SSL', - fields: () => ({ - id: globalIdField('ssl'), - acceptableCiphers: { - type: GraphQLList(GraphQLString), - description: - 'List of ciphers in use by the server deemed to be "acceptable".', - resolve: ({ acceptable_ciphers: acceptableCiphers }) => acceptableCiphers, - }, - acceptableCurves: { - type: GraphQLList(GraphQLString), - description: - 'List of curves in use by the server deemed to be "acceptable".', - resolve: ({ acceptable_curves: acceptableCurves }) => acceptableCurves, - }, - ccsInjectionVulnerable: { - type: GraphQLBoolean, - description: 'Denotes vulnerability to OpenSSL CCS Injection.', - resolve: ({ ccs_injection_vulnerable: ccsInjectionVulnerable }) => - ccsInjectionVulnerable, - }, - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ domainId }, _, { loaders: { loadDomainByKey } }) => { - const domainKey = domainId.split('/')[1] - const domain = await loadDomainByKey.load(domainKey) - domain.id = domain._key - return domain - }, - }, - heartbleedVulnerable: { - type: GraphQLBoolean, - description: 'Denotes vulnerability to "Heartbleed" exploit.', - resolve: ({ heartbleed_vulnerable: heartbleedVulnerable }) => - heartbleedVulnerable, - }, - rawJson: { - type: GraphQLJSON, - description: 'Raw scan result.', - resolve: ({ rawJson }) => JSON.stringify(rawJson), - }, - strongCiphers: { - type: GraphQLList(GraphQLString), - description: - 'List of ciphers in use by the server deemed to be "strong".', - resolve: ({ strong_ciphers: strongCiphers }) => strongCiphers, - }, - strongCurves: { - type: GraphQLList(GraphQLString), - description: 'List of curves in use by the server deemed to be "strong".', - resolve: ({ strong_curves: strongCurves }) => strongCurves, - }, - supportsEcdhKeyExchange: { - type: GraphQLBoolean, - description: 'Denotes support for elliptic curve key pairs.', - resolve: ({ supports_ecdh_key_exchange: supportsEcdhKeyExchange }) => - supportsEcdhKeyExchange, - }, - timestamp: { - type: GraphQLDate, - description: `The time when the scan was initiated.`, - resolve: ({ timestamp }) => new Date(timestamp), - }, - weakCiphers: { - type: GraphQLList(GraphQLString), - description: - 'List of ciphers in use by the server deemed to be "weak" or in other words, are not compliant with security standards.', - resolve: ({ weak_ciphers: weakCiphers }) => weakCiphers, - }, - weakCurves: { - type: GraphQLList(GraphQLString), - description: - 'List of curves in use by the server deemed to be "weak" or in other words, are not compliant with security standards.', - resolve: ({ weak_curves: weakCurves }) => weakCurves, - }, - guidanceTags: { - type: guidanceTagConnection.connectionType, - deprecationReason: - 'This has been sub-divided into neutral, negative, and positive tags.', - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Guidance tags found during scan.`, - resolve: async ( - { guidanceTags }, - args, - { loaders: { loadSslGuidanceTagConnectionsByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagConnectionsByTagId({ - sslGuidanceTags: guidanceTags, - ...args, - }) - return sslTags - }, - }, - negativeGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Negative guidance tags found during scan.`, - resolve: async ( - { negativeTags }, - args, - { loaders: { loadSslGuidanceTagConnectionsByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagConnectionsByTagId({ - sslGuidanceTags: negativeTags, - ...args, - }) - return sslTags - }, - }, - neutralGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Neutral guidance tags found during scan.`, - resolve: async ( - { neutralTags }, - args, - { loaders: { loadSslGuidanceTagConnectionsByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagConnectionsByTagId({ - sslGuidanceTags: neutralTags, - ...args, - }) - return sslTags - }, - }, - positiveGuidanceTags: { - type: guidanceTagConnection.connectionType, - args: { - orderBy: { - type: guidanceTagOrder, - description: 'Ordering options for guidance tag connections', - }, - ...connectionArgs, - }, - description: `Positive guidance tags found during scan.`, - resolve: async ( - { positiveTags }, - args, - { loaders: { loadSslGuidanceTagConnectionsByTagId } }, - ) => { - const sslTags = await loadSslGuidanceTagConnectionsByTagId({ - sslGuidanceTags: positiveTags, - ...args, - }) - return sslTags - }, - }, - }), - interfaces: [nodeInterface], - description: `Secure Socket Layer scan results.`, -}) diff --git a/api-js/src/web-scan/objects/web-scan.js b/api-js/src/web-scan/objects/web-scan.js deleted file mode 100644 index 1fb05945b5..0000000000 --- a/api-js/src/web-scan/objects/web-scan.js +++ /dev/null @@ -1,84 +0,0 @@ -import { GraphQLObjectType } from 'graphql' -import { connectionArgs } from 'graphql-relay' -import { GraphQLDate } from 'graphql-scalars' - -import { domainType } from '../../domain/objects' -import { httpsOrder, sslOrder } from '../inputs' -import { httpsConnection } from './https-connection' -import { sslConnection } from './ssl-connection' - -export const webScanType = new GraphQLObjectType({ - name: 'WebScan', - fields: () => ({ - domain: { - type: domainType, - description: `The domain the scan was ran on.`, - resolve: async ({ _key }, _, { loaders: { loadDomainByKey } }) => { - const domain = await loadDomainByKey.load(_key) - domain.id = domain._key - return domain - }, - }, - https: { - type: httpsConnection.connectionType, - args: { - startDate: { - type: GraphQLDate, - description: 'Start date for date filter.', - }, - endDate: { - type: GraphQLDate, - description: 'End date for date filter.', - }, - orderBy: { - type: httpsOrder, - description: 'Ordering options for https connections.', - }, - ...connectionArgs, - }, - description: `Hyper Text Transfer Protocol Secure scan results.`, - resolve: async ( - { _id }, - args, - { loaders: { loadHttpsConnectionsByDomainId } }, - ) => { - const https = await loadHttpsConnectionsByDomainId({ - domainId: _id, - ...args, - }) - return https - }, - }, - ssl: { - type: sslConnection.connectionType, - args: { - startDate: { - type: GraphQLDate, - description: 'Start date for date filter.', - }, - endDate: { - type: GraphQLDate, - description: 'End date for date filter.', - }, - orderBy: { - type: sslOrder, - description: 'Ordering options for ssl connections.', - }, - ...connectionArgs, - }, - description: `Secure Socket Layer scan results.`, - resolve: async ( - { _id }, - args, - { loaders: { loadSslConnectionByDomainId } }, - ) => { - const ssl = await loadSslConnectionByDomainId({ - domainId: _id, - ...args, - }) - return ssl - }, - }, - }), - description: `Results of HTTPS, and SSL scan on the given domain.`, -}) diff --git a/api-js/src/web-scan/subscriptions/__tests__/https-scan-data.test.js b/api-js/src/web-scan/subscriptions/__tests__/https-scan-data.test.js deleted file mode 100644 index 2e228fd13a..0000000000 --- a/api-js/src/web-scan/subscriptions/__tests__/https-scan-data.test.js +++ /dev/null @@ -1,342 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import Redis from 'ioredis' -import { - graphql, - GraphQLSchema, - subscribe, - parse, - GraphQLObjectType, - GraphQLInt, - GraphQLID, -} from 'graphql' -import { RedisPubSub } from 'graphql-redis-subscriptions' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadHttpsGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' - -const { - REDIS_PORT_NUMBER, - REDIS_DOMAIN_NAME, - HTTPS_SCAN_CHANNEL, - DB_PASS: rootPass, - DB_URL: url, -} = process.env - -describe('given the httpsScanData subscription', () => { - let pubsub, - schema, - publisherClient, - subscriberClient, - query, - truncate, - collections, - drop, - options, - httpsScan, - createSubscriptionMutation, - redis, - pub, - domain, - sharedId, - status - - beforeAll(async () => { - options = { - host: REDIS_DOMAIN_NAME, - port: REDIS_PORT_NUMBER, - } - - httpsScan = { - implementation: 'Valid HTTPS', - enforced: 'Strict', - hsts: 'No HSTS', - hstsAge: null, - preloaded: 'HSTS Not Preloaded', - rawJson: { - missing: true, - }, - negativeTags: ['https1'], - neutralTags: ['https1'], - positiveTags: ['https1'], - } - - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - - publisherClient = new Redis(options) - subscriberClient = new Redis(options) - redis = new Redis(options) - pub = new Redis(options) - - pubsub = new RedisPubSub({ - publisher: publisherClient, - subscriber: subscriberClient, - }) - await collections.httpsGuidanceTags.save({ - _key: 'https1', - en: { - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - sharedId = 'some-shared-id' - status = 'pass' - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await publisherClient.quit() - await subscriberClient.quit() - await redis.quit() - await pub.quit() - await drop() - }) - - it('returns the subscription data', async () => { - createSubscriptionMutation = () => - new GraphQLObjectType({ - name: 'Mutation', - fields: () => ({ - testMutation: { - type: GraphQLInt, - args: { - subscriptionId: { - type: GraphQLID, - }, - }, - resolve: async (_source, { subscriptionId }) => { - await redis.subscribe( - `${HTTPS_SCAN_CHANNEL}/${subscriptionId}`, - (_err, _count) => { - pub.publish( - `${HTTPS_SCAN_CHANNEL}/${subscriptionId}`, - JSON.stringify({ - sharedId: sharedId, - domainKey: domain._key, - status: status, - results: httpsScan, - }), - ) - }, - ) - - return 1 - }, - }, - }), - }) - - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createSubscriptionMutation(), - subscription: createSubscriptionSchema(), - }) - - const triggerSubscription = setTimeout(() => { - graphql( - schema, - ` - mutation { - testMutation(subscriptionId: "uuid-1234") - } - `, - null, - { - Redis, - options, - }, - ) - }, 100) - - const data = await subscribe( - schema, - parse(` - subscription { - httpsScanData { - sharedId - domain { - domain - } - status - implementation - enforced - hsts - hstsAge - preloaded - rawJson - negativeGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - neutralGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - positiveGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - } - } - `), - triggerSubscription, - { - pubsubs: { - httpsPubSub: pubsub, - }, - userKey: 'uuid-1234', - loaders: { - loadDomainByKey: loadDomainByKey({ query, userKey: '1', i18n: {} }), - loadHttpsGuidanceTagByTagId: loadHttpsGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }), - }, - }, - {}, - ) - - const result = await data.next() - - const expectedResult = { - data: { - httpsScanData: { - sharedId: sharedId, - domain: { - domain: 'test.domain.gc.ca', - }, - status: status.toUpperCase(), - implementation: 'Valid HTTPS', - enforced: 'Strict', - hsts: 'No HSTS', - hstsAge: null, - preloaded: 'HSTS Not Preloaded', - rawJson: '{"missing":true}', - negativeGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'https1'), - tagId: 'https1', - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - neutralGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'https1'), - tagId: 'https1', - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - positiveGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'https1'), - tagId: 'https1', - tagName: 'HTTPS-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - }, - }, - } - - expect(result.value).toEqual(expectedResult) - }) -}) diff --git a/api-js/src/web-scan/subscriptions/__tests__/ssl-scan-data.test.js b/api-js/src/web-scan/subscriptions/__tests__/ssl-scan-data.test.js deleted file mode 100644 index eea1afebf4..0000000000 --- a/api-js/src/web-scan/subscriptions/__tests__/ssl-scan-data.test.js +++ /dev/null @@ -1,371 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import Redis from 'ioredis' -import { - graphql, - GraphQLSchema, - subscribe, - parse, - GraphQLObjectType, - GraphQLInt, - GraphQLID, -} from 'graphql' -import { RedisPubSub } from 'graphql-redis-subscriptions' -import { toGlobalId } from 'graphql-relay' - -import { databaseOptions } from '../../../../database-options' -import { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadSslGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' - -const { - REDIS_PORT_NUMBER, - REDIS_DOMAIN_NAME, - SSL_SCAN_CHANNEL, - DB_PASS: rootPass, - DB_URL: url, -} = process.env - -describe('given the spfScanData subscription', () => { - let pubsub, - schema, - publisherClient, - subscriberClient, - query, - truncate, - collections, - drop, - options, - sslScan, - createSubscriptionMutation, - redis, - pub, - domain, - sharedId, - status - - beforeAll(async () => { - options = { - host: REDIS_DOMAIN_NAME, - port: REDIS_PORT_NUMBER, - } - - sslScan = { - acceptable_ciphers: [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - acceptable_curves: ['curve123'], - ccs_injection_vulnerable: false, - heartbleed_vulnerable: false, - strong_ciphers: [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - strong_curves: ['curve123'], - supports_ecdh_key_exchange: false, - weak_ciphers: [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - weak_curves: ['curve123'], - rawJson: { - missing: true, - }, - negativeTags: ['ssl1'], - neutralTags: ['ssl1'], - positiveTags: ['ssl1'], - } - - // Generate DB Items - ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), - })) - - publisherClient = new Redis(options) - subscriberClient = new Redis(options) - redis = new Redis(options) - pub = new Redis(options) - - pubsub = new RedisPubSub({ - publisher: publisherClient, - subscriber: subscriberClient, - }) - await collections.sslGuidanceTags.save({ - _key: 'ssl1', - en: { - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinksGuide: [ - { - description: 'refLinksGuide Description', - ref_link: 'www.refLinksGuide.ca', - }, - ], - refLinksTechnical: [ - { - description: 'refLinksTechnical Description', - ref_link: 'www.refLinksTechnical.ca', - }, - ], - }, - }) - domain = await collections.domains.save({ - domain: 'test.domain.gc.ca', - slug: 'test-domain-gc-ca', - }) - sharedId = 'some-shared-id' - status = 'pass' - }) - - afterEach(async () => { - await truncate() - }) - - afterAll(async () => { - await publisherClient.quit() - await subscriberClient.quit() - await redis.quit() - await pub.quit() - await drop() - }) - - it('returns the subscription data', async () => { - createSubscriptionMutation = () => - new GraphQLObjectType({ - name: 'Mutation', - fields: () => ({ - testMutation: { - type: GraphQLInt, - args: { - subscriptionId: { - type: GraphQLID, - }, - }, - resolve: async (_source, { subscriptionId }) => { - await redis.subscribe( - `${SSL_SCAN_CHANNEL}/${subscriptionId}`, - (_err, _count) => { - pub.publish( - `${SSL_SCAN_CHANNEL}/${subscriptionId}`, - JSON.stringify({ - sharedId: sharedId, - domainKey: domain._key, - status: status, - results: sslScan, - }), - ) - }, - ) - return 1 - }, - }, - }), - }) - - schema = new GraphQLSchema({ - query: createQuerySchema(), - mutation: createSubscriptionMutation(), - subscription: createSubscriptionSchema(), - }) - - const triggerSubscription = setTimeout(() => { - graphql( - schema, - ` - mutation { - testMutation(subscriptionId: "uuid-1234") - } - `, - null, - { - Redis, - options, - }, - ) - }, 100) - - const data = await subscribe( - schema, - parse(` - subscription { - sslScanData { - sharedId - domain { - domain - } - status - acceptableCiphers - acceptableCurves - ccsInjectionVulnerable - heartbleedVulnerable - strongCiphers - strongCurves - supportsEcdhKeyExchange - weakCiphers - weakCurves - rawJson - negativeGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - neutralGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - positiveGuidanceTags { - id - tagId - tagName - guidance - refLinks { - description - refLink - } - refLinksTech { - description - refLink - } - } - } - } - `), - triggerSubscription, - { - pubsubs: { - sslPubSub: pubsub, - }, - userKey: 'uuid-1234', - loaders: { - loadDomainByKey: loadDomainByKey({ query, userKey: '1', i18n: {} }), - loadSslGuidanceTagByTagId: loadSslGuidanceTagByTagId({ - query, - userKey: '1', - i18n: {}, - language: 'en', - }), - }, - }, - {}, - ) - - const result = await data.next() - - const expectedResult = { - data: { - sslScanData: { - sharedId: sharedId, - domain: { - domain: 'test.domain.gc.ca', - }, - status: status.toUpperCase(), - acceptableCiphers: [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - acceptableCurves: ['curve123'], - ccsInjectionVulnerable: false, - heartbleedVulnerable: false, - strongCiphers: [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - strongCurves: ['curve123'], - supportsEcdhKeyExchange: false, - weakCiphers: [ - 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', - ], - weakCurves: ['curve123'], - rawJson: '{"missing":true}', - negativeGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'ssl1'), - tagId: 'ssl1', - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - neutralGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'ssl1'), - tagId: 'ssl1', - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - positiveGuidanceTags: [ - { - id: toGlobalId('guidanceTag', 'ssl1'), - tagId: 'ssl1', - tagName: 'SSL-TAG', - guidance: 'Some Interesting Guidance', - refLinks: [ - { - description: 'refLinksGuide Description', - refLink: 'www.refLinksGuide.ca', - }, - ], - refLinksTech: [ - { - description: 'refLinksTechnical Description', - refLink: 'www.refLinksTechnical.ca', - }, - ], - }, - ], - }, - }, - } - - expect(result.value).toEqual(expectedResult) - }) -}) diff --git a/api-js/src/web-scan/subscriptions/https-scan-data.js b/api-js/src/web-scan/subscriptions/https-scan-data.js deleted file mode 100644 index d579c60f8c..0000000000 --- a/api-js/src/web-scan/subscriptions/https-scan-data.js +++ /dev/null @@ -1,17 +0,0 @@ -import { httpsSubType } from '../objects' - -const { HTTPS_SCAN_CHANNEL } = process.env - -export const httpsScanData = { - type: httpsSubType, - description: - 'This subscription allows the user to receive https data directly from the scanners in real time.', - resolve: ({ sharedId, domainKey, results, status }) => ({ - sharedId, - domainKey, - status, - ...results, - }), - subscribe: async (_context, _args, { pubsubs: { httpsPubSub }, userKey }) => - httpsPubSub.asyncIterator(`${HTTPS_SCAN_CHANNEL}/${userKey}`), -} diff --git a/api-js/src/web-scan/subscriptions/index.js b/api-js/src/web-scan/subscriptions/index.js deleted file mode 100644 index 512218607a..0000000000 --- a/api-js/src/web-scan/subscriptions/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './https-scan-data' -export * from './ssl-scan-data' diff --git a/api-js/src/web-scan/subscriptions/ssl-scan-data.js b/api-js/src/web-scan/subscriptions/ssl-scan-data.js deleted file mode 100644 index a8fe970dd6..0000000000 --- a/api-js/src/web-scan/subscriptions/ssl-scan-data.js +++ /dev/null @@ -1,17 +0,0 @@ -import { sslSubType } from '../objects' - -const { SSL_SCAN_CHANNEL } = process.env - -export const sslScanData = { - type: sslSubType, - description: - 'This subscription allows the user to receive ssl data directly from the scanners in real time.', - resolve: ({ sharedId, domainKey, results, status }) => ({ - sharedId, - domainKey, - status, - ...results, - }), - subscribe: async (_context, _args, { pubsubs: { sslPubSub }, userKey }) => - sslPubSub.asyncIterator(`${SSL_SCAN_CHANNEL}/${userKey}`), -} diff --git a/api-js/.babelrc b/api/.babelrc similarity index 100% rename from api-js/.babelrc rename to api/.babelrc diff --git a/api-js/.dockerignore b/api/.dockerignore similarity index 100% rename from api-js/.dockerignore rename to api/.dockerignore diff --git a/api/.env.example b/api/.env.example new file mode 100644 index 0000000000..d55a990ee9 --- /dev/null +++ b/api/.env.example @@ -0,0 +1,30 @@ +AUTHENTICATED_KEY= +AUTH_TOKEN_EXPIRY= +CIPHER_KEY= +COST_LIMIT= +DB_NAME= +DB_PASS= +DB_URL= +DEPTH_LIMIT= +HASHING_SALT= +LIST_FACTOR= +LOGIN_REQUIRED= +NATS_URL= +NOTIFICATION_API_KEY= +NOTIFICATION_API_URL= +NOTIFICATION_AUTHENTICATE_EMAIL_ID= +NOTIFICATION_AUTHENTICATE_TEXT_ID= +NOTIFICATION_ORG_INVITE_BILINGUAL= +NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL= +NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL= +NOTIFICATION_PASSWORD_RESET_BILINGUAL= +NOTIFICATION_TWO_FACTOR_CODE_EN= +NOTIFICATION_TWO_FACTOR_CODE_FR= +NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL= +OBJECT_COST= +REFRESH_KEY= +REFRESH_TOKEN_EXPIRY= +SCALAR_COST= +SERVICE_ACCOUNT_EMAIL= +SIGN_IN_KEY= +TRACING_ENABLED= diff --git a/api-js/.eslintignore b/api/.eslintignore similarity index 100% rename from api-js/.eslintignore rename to api/.eslintignore diff --git a/api-js/.eslintrc b/api/.eslintrc similarity index 100% rename from api-js/.eslintrc rename to api/.eslintrc diff --git a/api-js/.prettierrc b/api/.prettierrc similarity index 100% rename from api-js/.prettierrc rename to api/.prettierrc diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000000..aacb3cd5f2 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,38 @@ +# To build image: docker build --tag tracker-api:1.0 . +# To run image: docker run --network=host --env-file ./.env tracker-api:1.0 +# Build image +FROM node:20.20.0-alpine3.22 AS base + +WORKDIR /app + +FROM base AS builder + +COPY package*.json .babelrc ./ + +RUN npm ci + +COPY ./src ./src +COPY ./index.js . +COPY ./lingui.config.js . +COPY ./.env.example . + +RUN npm run build + +RUN npm prune --production + +# Prod image +FROM base AS release + +ENV NODE_ENV production + +COPY --from=builder /app/package.json . +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/.env.example . +COPY --from=builder /app/index.js . +COPY --from=builder /app/lingui.config.js . +COPY --from=builder /app/dist ./dist + +USER node +EXPOSE 4000 + +CMD ["npm", "start", "--node-options=--dns-result-order=ipv4first"] diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000000..d676dd84e2 --- /dev/null +++ b/api/README.md @@ -0,0 +1,154 @@ +# Tracker API + +The Tracker API is exclusively focused on serving data, rather than HTML. It is a GraphQL API, chosen because of its [composability](https://en.wikipedia.org/wiki/Composability), [legibility](https://www.ribbonfarm.com/2010/07/26/a-big-little-idea-called-legibility/) and for the way it [enables both security and security automation](https://www.youtube.com/watch?v=gqvyCdyp3Nw). +It is built with the [Express webserver](https://expressjs.com/) using the [express-graphql middleware](https://github.com/graphql/express-graphql), and follows the [Relay specifications for pagination](https://relay.dev/graphql/connections.htm). + +#### Installing Dependencies + +```shell +npm install +``` + +#### Running API Server + +In accordance with the [12Factor app](https://12factor.net) philosophy, the server [draws it's config from the environment](https://12factor.net/config). It does so based on a `.env` file that should exist in the root of the API folder which can be created with the following command, obviously modifying the test values shown to suit your setup. + +```bash +cat <<'EOF' > test.env +DB_PASS=test +DB_URL=http://localhost:8529 +DB_NAME=track_dmarc +AUTHENTICATED_KEY=12341234 +REFRESH_KEY=12341234 +SIGN_IN_KEY=12341234 +AUTH_TOKEN_EXPIRY=60 +REFRESH_TOKEN_EXPIRY=7 +LOGIN_REQUIRED=true +NOTIFICATION_API_KEY=asdf1234 +NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca +NOTIFICATION_AUTHENTICATE_EMAIL_ID=test_id +NOTIFICATION_AUTHENTICATE_TEXT_ID=test_id +NOTIFICATION_TWO_FACTOR_CODE_EN=test_id +NOTIFICATION_TWO_FACTOR_CODE_FR=test_id +NOTIFICATION_VERIFICATION_EMAIL_EN=test_id +NOTIFICATION_VERIFICATION_EMAIL_FR=test_id +DEPTH_LIMIT=15 +COST_LIMIT=10000 +SCALAR_COST=1 +OBJECT_COST=1 +LIST_FACTOR=1 +CIPHER_KEY=1234averyveryveryveryverylongkey +TRACING_ENABLED=false +HASHING_SALT=secret-salt +NATS_URL=nats://nats:4222 +NOTIFICATION_ORG_INVITE_BILINGUAL=test_id +NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL=test_id +NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL=test_id +NOTIFICATION_PASSWORD_RESET_BILINGUAL=test_id +SERVICE_ACCOUNT_EMAIL=service-account@email.ca +EOF +``` + +With that defined you can start the server with these commands. + +```shell +# Compile the scripts +npm run build +# Run the server +npm start +``` + +An online IDE will be accessible at [localhost:4000/graphql](http://localhost:4000/graphql) allowing you to explore the API. + +### Dev Workflow + +#### Install Dev Dependencies + +```shell +npm install +``` + +#### Running Server with Nodemon + +```shell +npm run dev +``` + +#### Running Tests + +The tests require a copy of [ArangoDB](https://www.arangodb.com/) to be running locally. ArangoDB should have its own .env file, and the value of the root password should align with the value of `DB_PASS` in the APIs `test.env` file. + +```bash +# Write the arango test credentials into an env file: +echo ARANGO_ROOT_PASSWORD=test > arangodb.env +# Run a detached arangodb container using the root password from the env: +docker run -d -p=8529:8529 --env-file arangodb.env --name=arango arangodb +``` + +The tests also requires a copy of [Redis](https://redis.io/) to be running locally. + +```bash +docker run -d -p=6379:6379 --name=redis redis +``` + +With the databases running, we need create the environment variables the application needs, but with some test appropriate values. We can do that by creating `test.env` in the API root directory with the following command. + +```bash +cat <<'EOF' > test.env +DB_PASS=test +DB_URL=http://localhost:8529 +DB_NAME=track_dmarc +AUTHENTICATED_KEY=12341234 +REFRESH_KEY=12341234 +SIGN_IN_KEY=12341234 +AUTH_TOKEN_EXPIRY=60 +REFRESH_TOKEN_EXPIRY=7 +LOGIN_REQUIRED=true +NOTIFICATION_API_KEY=asdf1234 +NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca +NOTIFICATION_AUTHENTICATE_EMAIL_ID=test_id +NOTIFICATION_AUTHENTICATE_TEXT_ID=test_id +NOTIFICATION_TWO_FACTOR_CODE_EN=test_id +NOTIFICATION_TWO_FACTOR_CODE_FR=test_id +NOTIFICATION_VERIFICATION_EMAIL_EN=test_id +NOTIFICATION_VERIFICATION_EMAIL_FR=test_id +DEPTH_LIMIT=15 +COST_LIMIT=10000 +SCALAR_COST=1 +OBJECT_COST=1 +LIST_FACTOR=1 +CIPHER_KEY=1234averyveryveryveryverylongkey +TRACING_ENABLED=false +HASHING_SALT=secret-salt +NATS_URL=nats://nats:4222 +NOTIFICATION_ORG_INVITE_BILINGUAL=test_id +NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL=test_id +NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL=test_id +NOTIFICATION_PASSWORD_RESET_BILINGUAL=test_id +SERVICE_ACCOUNT_EMAIL=service-account@email.ca +EOF +``` + +Finally, run the tests. + +```bash +npm test +``` + +#### Checking Test Coverage + +```shell +npm run test-coverage +``` + +#### Running ESLint + +```shell +npm run lint +``` + +#### Formatting Code with Prettier + +```shell +npm run prettier +``` diff --git a/api/cloudbuild.yaml b/api/cloudbuild.yaml new file mode 100644 index 0000000000..c3ee4b9bf0 --- /dev/null +++ b/api/cloudbuild.yaml @@ -0,0 +1,143 @@ +steps: + - name: 'gcr.io/cloud-builders/docker' + id: start_arango + entrypoint: /bin/sh + args: + [ + '-c', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.12.1', + ] + + - name: mikewilliamson/wait-for + id: wait_arango + args: ['arangodb:8529'] + + - name: node:20-alpine + id: install + dir: api + entrypoint: npm + args: ['ci'] + + - name: node:20-alpine + id: lint + dir: api + entrypoint: npm + args: ['run', lint] + + - name: node:20-alpine + id: lingui-extract + dir: api + entrypoint: ash + args: + - '-c' + - | + npm run extract | tee /workspace/lingui-extract-output.txt + + - name: node:20-alpine + id: lingui-translation-check + dir: api + entrypoint: ash + args: + - '-c' + - | + output=$( awk -F '│' '/fr/ { gsub(/^[ \t]+|[ \t]+$/, "", $4); print $4 }' /workspace/lingui-extract-output.txt ) + + # Check if there are any missed french translations + if [ "$output" -eq 0 ]; then + echo "All translations are up to date, proceeding." + else + echo "There are $output missed translations, please update them before proceeding." + exit 1 + fi + + - name: node:20-alpine + id: lingui-compile + dir: api + entrypoint: npm + args: ['run', 'compile'] + + - name: node:20-alpine + id: check-src-files + dir: api + entrypoint: ash + args: + - '-c' + - | + "=== checking src directory content ===" + ls -la src + + - name: node:20-alpine + id: test + dir: api + entrypoint: npm + args: ['test'] + env: + - DB_PASS=$_DB_PASS + - DB_URL=$_DB_URL + - DB_NAME=$_DB_NAME + - AUTHENTICATED_KEY=$_AUTHENTICATED_KEY + - REFRESH_KEY=$_REFRESH_KEY + - SIGN_IN_KEY=$_SIGN_IN_KEY + - AUTH_TOKEN_EXPIRY=$_AUTH_TOKEN_EXPIRY + - REFRESH_TOKEN_EXPIRY=$_REFRESH_TOKEN_EXPIRY + - LOGIN_REQUIRED=$_LOGIN_REQUIRED + - NOTIFICATION_API_KEY=$_NOTIFICATION_API_KEY + - NOTIFICATION_API_URL=$_NOTIFICATION_API_URL + - NOTIFICATION_AUTHENTICATE_EMAIL_ID=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_AUTHENTICATE_TEXT_ID=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_PASSWORD_RESET_BILINGUAL=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_TWO_FACTOR_CODE_EN=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_TWO_FACTOR_CODE_FR=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_ORG_INVITE_BILINGUAL=$_NOTIFICATION_TEST_TEMPLATE_ID + - DEPTH_LIMIT=$_DEPTH_LIMIT + - COST_LIMIT=$_COST_LIMIT + - SCALAR_COST=$_SCALAR_COST + - OBJECT_COST=$_OBJECT_COST + - LIST_FACTOR=$_LIST_FACTOR + - CIPHER_KEY=$_CIPHER_KEY + - TRACING_ENABLED=$_TRACING_ENABLED + - HASHING_SALT=$_HASHING_SALT + - LOGIN_REQUIRED=false + - NATS_URL=$_NATS_URL + - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL=$_NOTIFICATION_TEST_TEMPLATE_ID + - SERVICE_ACCOUNT_EMAIL=$_SERVICE_ACCOUNT_EMAIL + - TRACKER_PRODUCTION=true + + - name: 'gcr.io/cloud-builders/docker' + id: generate-image-name + entrypoint: 'bash' + dir: api + args: + - '-c' + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/api-js:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: 'gcr.io/cloud-builders/docker' + id: build + entrypoint: 'bash' + dir: api + args: + - '-c' + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: 'gcr.io/cloud-builders/docker' + id: push-if-master + entrypoint: 'bash' + args: + - '-c' + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi + +timeout: 1200s +options: + machineType: 'E2_HIGHCPU_8' diff --git a/api/database.json b/api/database.json new file mode 120000 index 0000000000..d19d64174f --- /dev/null +++ b/api/database.json @@ -0,0 +1 @@ +../database-migration/database.json \ No newline at end of file diff --git a/api/docker-compose.yaml b/api/docker-compose.yaml new file mode 100644 index 0000000000..45fe2ddf26 --- /dev/null +++ b/api/docker-compose.yaml @@ -0,0 +1,75 @@ +version: '3' + +services: + nats: + image: nats:2.10.16-scratch + container_name: nats + network_mode: "host" + command: -js + restart: always + + nats-box: + image: natsio/nats-box:latest + container_name: nats-box + depends_on: + - nats + network_mode: "host" + volumes: + - ../k8s/infrastructure/bases/nats/stream-config.json:/stream-config/stream-config.json + entrypoint: > + /bin/sh -c ' + set -e + echo "[nats-stream-init] Waiting for NATS..." + until nc -z localhost 4222; do + sleep 1 + done + + # Small additional delay to ensure JetStream is ready + sleep 2 + + # ==================== CREATE/UPDATE STREAM ==================== + echo "[nats-stream-init] Attempting to add SCANS stream..." + if nats stream add SCANS --config /stream-config/stream-config.json --server nats://localhost:4222; then + echo "[nats-stream-init] SCANS stream added successfully." + else + echo "[nats-stream-init] Add failed, attempting to edit SCANS stream..." + if nats stream edit SCANS --config /stream-config/stream-config.json --server nats://localhost:4222 --force; then + echo "[nats-stream-init] SCANS stream edited successfully." + else + echo "[nats-stream-init] ERROR: Could not add or edit SCANS stream!" + exit 1 + fi + fi + + # ==================== CREATE/UPDATE KV STORE ==================== + + echo "[nats-stream-init] Attempting to add WEB_SCANNER_IPS KV bucket..." + + if nats kv add "WEB_SCANNER_IPS" \ + --ttl="5m"; then + echo "[nats-stream-init] WEB_SCANNER_IPS KV bucket added successfully." + else + echo "[nats-stream-init] Add failed, attempting to edit WEB_SCANNER_IPS KV bucket..." + + if nats kv edit "WEB_SCANNER_IPS" \ + --ttl="5m"; then + echo "[nats-stream-init] WEB_SCANNER_IPS KV bucket edited successfully." + else + echo "[nats-stream-init] ERROR: Could not add or edit WEB_SCANNER_IPS KV bucket!" + exit 1 + fi + fi + + echo "[nats-stream-init] Stream initialization complete." + exec sleep infinity + ' + + arangodb: + image: arangodb:3.12.1 + container_name: arangodb + environment: + - ARANGO_ROOT_PASSWORD=test + network_mode: "host" + restart: unless-stopped + volumes: + - ${TRACKER_BACKUP_DIR}:/var/lib/arangodb3 diff --git a/api/index.js b/api/index.js new file mode 100644 index 0000000000..89ba00fc90 --- /dev/null +++ b/api/index.js @@ -0,0 +1,94 @@ +import 'dotenv-safe/config' +import { Database, aql } from 'arangojs' +import { Server } from './src/server' +import { createContext } from './src/create-context' +import { createI18n } from './src/create-i18n' +import { connect, JSONCodec } from 'nats' +import { collectionNames as collections } from './src/collection-names' + +const { + PORT = 4000, + DB_PASS: rootPass, + DB_URL: url, + DB_NAME: databaseName, + DEPTH_LIMIT: maxDepth, + COST_LIMIT: complexityCost, + SCALAR_COST: scalarCost, + OBJECT_COST: objectCost, + LIST_FACTOR: listFactor, + TRACING_ENABLED: tracing, + HASHING_SALT, + LOGIN_REQUIRED = 'true', + NATS_URL, +} = process.env + +;(async () => { + const db = new Database({ + url, + databaseName, + auth: { username: 'root', password: rootPass }, + }) + + const query = async function query(strings, ...vars) { + return db.query(aql(strings, ...vars), { + count: true, + }) + } + + const transaction = async function transaction(collections) { + return db.beginTransaction(collections) + } + + const nc = await connect({ servers: NATS_URL, maxReconnectAttempts: -1, reconnectTimeWait: 1000 }) + + // create a jetstream client: + const js = nc.jetstream() + + // eslint-disable-next-line new-cap + const jc = JSONCodec() + + const publish = async ({ channel, msg, options = {} }) => { + await js.publish(channel, jc.encode(msg), options) + } + + const server = await Server({ + context: async ({ req, res, connection }) => { + if (connection) { + // XXX: assigning over req? + req = { + headers: { + authorization: connection.authorization, + }, + language: connection.language, + } + } + const i18n = createI18n(req.language) + return createContext({ + query, + db, + transaction, + collections, + publish, + req, + res, + language: req.language, + i18n, + loginRequiredBool: LOGIN_REQUIRED === 'true', // bool not string + salt: HASHING_SALT, + }) + }, + maxDepth, + complexityCost, + scalarCost, + objectCost, + listFactor, + tracing, + }) + + console.log(`Starting server with "LOGIN_REQUIRED" set to "${LOGIN_REQUIRED}"`) + + await server.listen(PORT, (err) => { + if (err) throw err + console.log(`🚀 Server ready at http://localhost:${PORT}/graphql`) + }) +})() diff --git a/api/jest.config.js b/api/jest.config.js new file mode 100644 index 0000000000..1aeda50c87 --- /dev/null +++ b/api/jest.config.js @@ -0,0 +1,18 @@ +module.exports = { + setupFiles: ['/src/setupEnv.js'], + collectCoverage: false, + collectCoverageFrom: ['src/**/*.js'], + coveragePathIgnorePatterns: [ + 'node_modules', + 'test-config', + 'jestGlobalMocks.js', + '.module.js', + 'locale', + 'index.js', + 'env.js', + ], + moduleNameMapper: { + '^axios$': require.resolve('axios'), + }, + testTimeout: 120000, +} diff --git a/api-js/lingui.config.js b/api/lingui.config.js similarity index 89% rename from api-js/lingui.config.js rename to api/lingui.config.js index bd5edec8f7..1f1b28e62f 100644 --- a/api-js/lingui.config.js +++ b/api/lingui.config.js @@ -5,7 +5,6 @@ module.exports = { include: ['src'], }, ], - extractBabelOptions: {}, fallbackLocales: { default: 'en', }, diff --git a/api/package-lock.json b/api/package-lock.json new file mode 100644 index 0000000000..b1a2a46b5c --- /dev/null +++ b/api/package-lock.json @@ -0,0 +1,21734 @@ +{ + "name": "tracker-api", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "tracker-api", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@apollo/server": "^5.5.0", + "@as-integrations/express4": "^1.1.2", + "@lingui/core": "^4.13.0", + "accesscontrol": "^2.2.1", + "arango-tools": "^0.6.0", + "arangojs": "^10.2.2", + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.4", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dataloader": "^2.0.0", + "dotenv-safe": "^8.2.0", + "express": "^4.22.0", + "express-request-language": "^1.1.15", + "graphql": "^16.12.0", + "graphql-depth-limit": "^1.1.0", + "graphql-redis-subscriptions": "^2.6.0", + "graphql-relay": "^0.10.2", + "graphql-scalars": "^1.25.0", + "graphql-subscriptions": "^2.0.0", + "graphql-validation-complexity": "^0.4.2", + "ioredis": "^4.28.3", + "isomorphic-fetch": "^3.0.0", + "jsonwebtoken": "^9.0.2", + "make-plural": "^7.1.0", + "moment": "^2.29.4", + "ms": "^2.1.3", + "nats": "^2.18.0", + "notifications-node-client": "^8.2.1", + "url-slug": "^3.0.2", + "uuid": "^8.3.2", + "validator": "^13.15.22" + }, + "devDependencies": { + "@babel/cli": "^7.16.8", + "@babel/core": "^7.16.7", + "@babel/node": "^7.16.8", + "@babel/preset-env": "^7.16.8", + "@jest/test-sequencer": "^30.3.0", + "@lingui/cli": "^5.4.1", + "@lingui/macro": "^4.13.0", + "babel-core": "^7.0.0-bridge.0", + "babel-jest": "^30.3.0", + "babel-plugin-macros": "^3.1.0", + "babel-polyfill": "^6.26.0", + "eslint": "^8.57.1", + "eslint-config-prettier": "^8.3.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^29.15.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.6.0", + "jest": "^30.3.0", + "jest-fetch-mock": "^3.0.3", + "jest-matcher-utils": "^30.3.0", + "nodemon": "^3.1.11", + "prettier": "^2.5.1", + "supertest": "^7.0.0" + } + }, + "node_modules/@apollo/cache-control-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", + "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", + "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", + "dependencies": { + "@apollo/cache-control-types": "^1.0.3", + "@apollo/server-gateway-interface": "^2.0.0", + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.isnodelike": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0", + "@apollo/utils.usagereporting": "^2.1.0", + "@apollo/utils.withrequired": "^3.0.0", + "@graphql-tools/schema": "^10.0.0", + "async-retry": "^1.2.1", + "body-parser": "^2.2.2", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "finalhandler": "^2.1.0", + "loglevel": "^1.6.8", + "lru-cache": "^11.1.0", + "negotiator": "^1.0.0", + "uuid": "^11.1.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "graphql": "^16.11.0" + } + }, + "node_modules/@apollo/server-gateway-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz", + "integrity": "sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==", + "license": "MIT", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.dropunuseddefinitions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz", + "integrity": "sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.printwithreducedwhitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz", + "integrity": "sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.removealiases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz", + "integrity": "sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.sortast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz", + "integrity": "sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==", + "dependencies": { + "lodash.sortby": "^4.7.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.stripsensitiveliterals": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz", + "integrity": "sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/@apollo/utils.usagereporting": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz", + "integrity": "sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.0", + "@apollo/utils.dropunuseddefinitions": "^2.0.1", + "@apollo/utils.printwithreducedwhitespace": "^2.0.1", + "@apollo/utils.removealiases": "2.0.1", + "@apollo/utils.sortast": "^2.0.1", + "@apollo/utils.stripsensitiveliterals": "^2.0.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@apollo/server/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@apollo/server/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@apollo/server/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@apollo/server/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@apollo/server/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@apollo/server/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@apollo/server/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "dependencies": { + "@apollo/protobufjs": "1.2.7" + } + }, + "node_modules/@apollo/usage-reporting-protobuf/node_modules/@apollo/protobufjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", + "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", + "hasInstallScript": true, + "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/long": "^4.0.0", + "long": "^4.0.0" + }, + "bin": { + "apollo-pbjs": "bin/pbjs", + "apollo-pbts": "bin/pbts" + } + }, + "node_modules/@apollo/utils.createhash": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz", + "integrity": "sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==", + "license": "MIT", + "dependencies": { + "@apollo/utils.isnodelike": "^3.0.0", + "sha.js": "^2.4.11" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.fetcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz", + "integrity": "sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.isnodelike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz", + "integrity": "sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.keyvaluecache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "license": "MIT", + "dependencies": { + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@apollo/utils.keyvaluecache/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@apollo/utils.logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.withrequired": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz", + "integrity": "sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@as-integrations/express4": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@as-integrations/express4/-/express4-1.1.2.tgz", + "integrity": "sha512-PGeMcwoOKdYnZ4LtsmM7aLNoel3tbK8wKnfyahdRau1qb7wLbuaXB35zg3w34Ov4bm3WJtO3yzd8Bw5jVE+aIQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@apollo/server": "^4.0.0 || ^5.0.0", + "express": "^4.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz", + "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==", + "dev": true, + "dependencies": { + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.7.tgz", + "integrity": "sha512-kIFozAvVfK05DM4EVQYKK+zteWvY85BFdGBRQBytRyY3y+6PX0DkDOn/CZ3lEuczCfrCxEzwt0YtP/87YPTWSw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz", + "integrity": "sha512-fk5A6ymfp+O5+p2yCkXAu5Kyj6v0xh0RBeNcAkYUMDvvAAoxvSKXn+Jb37t/yWFiQVDFK1ELpUTD8/aLhCPu+g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^4.7.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", + "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/node": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.16.8.tgz", + "integrity": "sha512-V2dopEtPUL4LD+e8UtMIZB6BbsmMsS/7E1ZAvWNINzBfi7Cf3X9MLCpzHVZT4HeeF1lQl72IRtqqVt2RUImwyA==", + "dev": true, + "dependencies": { + "@babel/register": "^7.16.8", + "commander": "^4.0.1", + "core-js": "^3.20.2", + "node-environment-flags": "^1.0.5", + "regenerator-runtime": "^0.13.4", + "v8flags": "^3.1.1" + }, + "bin": { + "babel-node": "bin/babel-node.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", + "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", + "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", + "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", + "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", + "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.7.tgz", + "integrity": "sha512-3O0Y4+dw94HA86qSg9IHfyPktgR7q3gpNVAeiKQd+8jBKFaU5NQS1Yatgo4wY+UFNuLjvxcSmzcsHqrhgTyBUA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.7.tgz", + "integrity": "sha512-7twV3pzhrRxSwHeIvFE6coPgvo+exNDOiGUMg39o2LiLo1Y+4aKpfkcLGcg1UHonzorCt7SNXnoMyCnnIOA8Sw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", + "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", + "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", + "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", + "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", + "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", + "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", + "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", + "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", + "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", + "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", + "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", + "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", + "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", + "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", + "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", + "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", + "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "dev": true, + "dependencies": { + "regenerator-transform": "^0.14.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", + "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", + "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", + "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", + "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.8.tgz", + "integrity": "sha512-9rNKgVCdwHb3z1IlbMyft6yIXIeP3xz6vWvGaLHrJThuEIqWfHb0DNBH9VuTgnDfdbUDhkmkvMZS/YMCtP7Elg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-async-generator-functions": "^7.16.8", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.16.7", + "@babel/plugin-proposal-json-strings": "^7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-private-methods": "^7.16.7", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@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/plugin-transform-arrow-functions": "^7.16.7", + "@babel/plugin-transform-async-to-generator": "^7.16.8", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.16.7", + "@babel/plugin-transform-classes": "^7.16.7", + "@babel/plugin-transform-computed-properties": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.16.7", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.16.7", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.16.7", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.16.8", + "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-umd": "^7.16.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-new-target": "^7.16.7", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.16.7", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-reserved-words": "^7.16.7", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.16.7", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.16.7", + "@babel/plugin-transform-typeof-symbol": "^7.16.7", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.8", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.16.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.16.9.tgz", + "integrity": "sha512-jJ72wcghdRIlENfvALcyODhNoGE5j75cYHdC+aQMh6cU/P86tiiXTp9XYZct1UxUMo/4+BgQRyNZEGx0KWGS+g==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.0", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.6.tgz", + "integrity": "sha512-bTnP+4oom4nDjmkS3Ykbe+ljAp/RIiWP3R35COMmuucS24iQxGLa9Hn8VMkLIoaoPxgz6xk+dbC43jtkNsFoBw==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "optional": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "dependencies": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jest/core/node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "dependencies": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "dependencies": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@jest/reporters/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "dependencies": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "dependencies": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "dependencies": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.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.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lingui/babel-plugin-extract-messages": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-5.4.1.tgz", + "integrity": "sha512-sjkVaLyuK3ZW62mv5gU6pOdl3ZpwDReeSaNodJuf9LssbMIQPa5WOirTnMeBaalrQ8BA2srrRzQAWgsXPQVdXA==", + "dev": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/babel-plugin-lingui-macro": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-5.4.1.tgz", + "integrity": "sha512-9IO+PDvdneY8OCI8zvI1oDXpzryTMtyRv7uq9O0U1mFCvIPVd5dWQKQDu/CpgpYAc2+JG/izn5PNl9xzPc6ckw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.20.12", + "@babel/runtime": "^7.20.13", + "@babel/types": "^7.20.7", + "@lingui/conf": "5.4.1", + "@lingui/core": "5.4.1", + "@lingui/message-utils": "5.4.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/@lingui/babel-plugin-lingui-macro/node_modules/@lingui/core": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-5.4.1.tgz", + "integrity": "sha512-4FeIh56PH5vziPg2BYo4XYWWOHE4XaY/XR8Jakwn0/qwtLpydWMNVpZOpGWi7nfPZtcLaJLmZKup6UNxEl1Pfw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.4.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@lingui/babel-plugin-lingui-macro": "5.4.1", + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "@lingui/babel-plugin-lingui-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/@lingui/babel-plugin-lingui-macro/node_modules/@lingui/message-utils": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.4.1.tgz", + "integrity": "sha512-hXfL90fFBoKp5YgLaWo3HbJS/7q+WlWs7VwVbUxl4pa+YladqNZf08JoDeBUDtlEVx5a3bNUSACXHs2FZo12aw==", + "dev": true, + "dependencies": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/cli": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-5.4.1.tgz", + "integrity": "sha512-UAKA9Iz4zMDJS7fzWMZ4hzQWontrTBnI5XCsPm7ttB0Ed0F4Pwph/Vu7pg4bJdiYr4d6nqEpRWd9aTxcC15/IA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.0", + "@babel/generator": "^7.21.1", + "@babel/parser": "^7.22.0", + "@babel/runtime": "^7.21.0", + "@babel/types": "^7.21.2", + "@lingui/babel-plugin-extract-messages": "5.4.1", + "@lingui/babel-plugin-lingui-macro": "5.4.1", + "@lingui/conf": "5.4.1", + "@lingui/core": "5.4.1", + "@lingui/format-po": "5.4.1", + "@lingui/message-utils": "5.4.1", + "chokidar": "3.5.1", + "cli-table": "^0.3.11", + "commander": "^10.0.0", + "convert-source-map": "^2.0.0", + "date-fns": "^3.6.0", + "esbuild": "^0.25.1", + "glob": "^11.0.0", + "micromatch": "^4.0.7", + "normalize-path": "^3.0.0", + "ora": "^5.1.0", + "picocolors": "^1.1.1", + "pofile": "^1.1.4", + "pseudolocale": "^2.0.0", + "source-map": "^0.8.0-beta.0" + }, + "bin": { + "lingui": "dist/lingui.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/cli/node_modules/@lingui/core": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-5.4.1.tgz", + "integrity": "sha512-4FeIh56PH5vziPg2BYo4XYWWOHE4XaY/XR8Jakwn0/qwtLpydWMNVpZOpGWi7nfPZtcLaJLmZKup6UNxEl1Pfw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.4.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@lingui/babel-plugin-lingui-macro": "5.4.1", + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "@lingui/babel-plugin-lingui-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/@lingui/cli/node_modules/@lingui/message-utils": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.4.1.tgz", + "integrity": "sha512-hXfL90fFBoKp5YgLaWo3HbJS/7q+WlWs7VwVbUxl4pa+YladqNZf08JoDeBUDtlEVx5a3bNUSACXHs2FZo12aw==", + "dev": true, + "dependencies": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/cli/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@lingui/cli/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@lingui/cli/node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/@lingui/cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@lingui/cli/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@lingui/cli/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@lingui/cli/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@lingui/cli/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@lingui/cli/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@lingui/cli/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/@lingui/cli/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/@lingui/conf": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-5.4.1.tgz", + "integrity": "sha512-aDkj/bMSr/mCL8Nr1TS52v0GLCuVa4YqtRz+WvUCFZw/ovVInX0hKq1TClx/bSlhu60FzB/CbclxFMBw8aLVUg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.13", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^1.17.1", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/conf/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@lingui/conf/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@lingui/conf/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@lingui/core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-4.13.0.tgz", + "integrity": "sha512-kRqQWeEVoqNrDtEdyHPWGsAHRStN8ObYc5a1gdyuBhoj1zaoUS/DMK5C7B1ZeTtj6rCCmZRs6d2tN12hsZ2zJA==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "4.13.0", + "unraw": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@lingui/format-po": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-5.4.1.tgz", + "integrity": "sha512-IBVq3RRLNEVRzNZcdEw0qpM5NKX4e9wDmvJMorkR2OYrgTbhWx5gDYhXpEZ9yqtuEVhILMdriVNjAAUnDAJibA==", + "dev": true, + "dependencies": { + "@lingui/conf": "5.4.1", + "@lingui/message-utils": "5.4.1", + "date-fns": "^3.6.0", + "pofile": "^1.1.4" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/format-po/node_modules/@lingui/message-utils": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.4.1.tgz", + "integrity": "sha512-hXfL90fFBoKp5YgLaWo3HbJS/7q+WlWs7VwVbUxl4pa+YladqNZf08JoDeBUDtlEVx5a3bNUSACXHs2FZo12aw==", + "dev": true, + "dependencies": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/macro": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-4.13.0.tgz", + "integrity": "sha512-OjhaWOWbTCXHOOHNaGI0shMP3qrPjNZ19tpEx/iStAmJq64fkevx/HbDPI0uuqLX8v1NFWG/SzBMIQzJb5YOvA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.13", + "@babel/types": "^7.20.7", + "@lingui/conf": "4.13.0", + "@lingui/core": "4.13.0", + "@lingui/message-utils": "4.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@lingui/react": "^4.0.0", + "babel-plugin-macros": "2 || 3" + } + }, + "node_modules/@lingui/macro/node_modules/@lingui/conf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-4.13.0.tgz", + "integrity": "sha512-7NSinlzgSMKBezLsSM7DMwr0IpTHKr8nuSDpTZpI79+BhW+Xq38jPRQqMXdzItW8Cl/Lsdr3Y3MnYJIl8tADsQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.13", + "chalk": "^4.1.0", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^1.17.1", + "lodash.get": "^4.4.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@lingui/macro/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@lingui/macro/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@lingui/macro/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@lingui/message-utils": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-4.13.0.tgz", + "integrity": "sha512-tI/WBVZym+APwqk0O3xTaF0k+RQIv5E4PqGHdXqwbofycHly2C+izH+hg6UeNctc6jd19GRwqu/4ga9knkdAlQ==", + "dependencies": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@lingui/react": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-4.13.0.tgz", + "integrity": "sha512-5remR9rVwosiiX/RnEWETHA8cpqQiP7U87OXXMPz67LuyG3XP8RP+ic75rVn284DHLHgpjDbauz7vYIz855ZoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.13", + "@lingui/core": "4.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@messageformat/parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz", + "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==", + "dependencies": { + "moo": "^0.5.1" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/accept-language": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", + "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=", + "dependencies": { + "bcp47": "^1.1.2", + "stable": "^0.1.6" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accesscontrol": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/accesscontrol/-/accesscontrol-2.2.1.tgz", + "integrity": "sha512-52EvFk/J9EF+w4mYQoKnOTkEMj01R1U5n2fc1dai6x1xkgOks3DGkx01qQL2cKFxGmE4Tn1krAU3jJA9L1NMkg==", + "license": "MIT", + "dependencies": { + "notation": "^1.3.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arango-tools": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", + "integrity": "sha512-2XjPPddz7Vc07JrOyXFYGzXI+QZOAR+I0kiyklKBevWVTTDh1lYRetZh3X1fN+smibgL2DY3jKwHK14aSzVkSw==", + "dependencies": { + "arangojs": "^7.2.0", + "assign-deep": "^1.0.1", + "json-placeholder-replacer": "^1.0.35" + } + }, + "node_modules/arango-tools/node_modules/arangojs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.8.0.tgz", + "integrity": "sha512-aJFlMKlVr4sIO5GDMuykBVNVxWeZTkWDgYbbl9cIuxVctp8Lhs6dW5fr5MYlwAndnOEyi3bvbrhZIucly2IpWQ==", + "dependencies": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arango-tools/node_modules/file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/arango-tools/node_modules/mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "dependencies": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/arango-tools/node_modules/multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "dependencies": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/arangojs": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-10.2.2.tgz", + "integrity": "sha512-3Xllq5inTGjros0mBP9NFxrIW8Di0ldtFurLdrXy5z4NDVJPyJtnwUiiGrMPY21NuVu53wUDE23YN50jnX4epw==", + "dependencies": { + "@types/node": "^20.11.26" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "undici": ">=5.21.0" + }, + "peerDependenciesMeta": { + "undici": { + "optional": true + } + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "dependencies": { + "assign-symbols": "^2.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "dev": true, + "dependencies": { + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "dev": true, + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", + "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.0", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.1.tgz", + "integrity": "sha512-TihqEe4sQcb/QcPJvxe94/9RZuLQuF1+To4WqQcRvc+3J3gLCPIPgDKzGLG6zmQLfH3nn25heRuDNkS2KR4I8A==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.20.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz", + "integrity": "sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "node_modules/babel-polyfill/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/babel-polyfill/node_modules/regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcp47": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", + "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001675", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001675.tgz", + "integrity": "sha512-/wV1bQwPrkLiQMjaJF5yUMVM/VdRPOCU8QZ+PmG6uW6DvYSrNY1bpwHI/3mOcUosLaJCzYDi5o91IQB51ft6cg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, + "node_modules/core-js": { + "version": "3.20.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", + "integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dataloader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", + "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-safe": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", + "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.49", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz", + "integrity": "sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz", + "integrity": "sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.2", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.12.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-plugin-jest": { + "version": "29.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.1.tgz", + "integrity": "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.0.0" + }, + "engines": { + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <7.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.0.tgz", + "integrity": "sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-request-language": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/express-request-language/-/express-request-language-1.1.15.tgz", + "integrity": "sha512-KiLUdEZCcgwh8qfIvkCrhz1MMAFx/Xj4UcspN4zUxVdp+bp+yFvqUMmlyMHK2nC5JlQV7VK5uFOoS5LrArTL1A==", + "dependencies": { + "accept-language": "^3.0.4", + "bcp47": "^1.1.2" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-depth-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", + "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", + "dependencies": { + "arrify": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "graphql": "*" + } + }, + "node_modules/graphql-redis-subscriptions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.6.0.tgz", + "integrity": "sha512-hWAPkCNSFKpEOJBMLAcbpGl/gv+s1Yho2JIwP9MK2RlvqlWiqhAYqjA/HVRK86nkYsRkBwGgqvaNxtV1fAexBQ==", + "optionalDependencies": { + "ioredis": "^5.2.4" + }, + "peerDependencies": { + "graphql-subscriptions": "^1.0.0 || ^2.0.0" + } + }, + "node_modules/graphql-redis-subscriptions/node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/graphql-redis-subscriptions/node_modules/ioredis": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", + "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "optional": true, + "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" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/graphql-relay": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.2.tgz", + "integrity": "sha512-abybva1hmlNt7Y9pMpAzHuFnM2Mme/a2Usd8S4X27fNteLGRAECMYfhmsrpZFvGn3BhmBZugMXYW/Mesv3P1Kw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.15.0 || >= 15.9.0" + }, + "peerDependencies": { + "graphql": "^16.2.0" + } + }, + "node_modules/graphql-scalars": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.25.0.tgz", + "integrity": "sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/graphql-subscriptions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz", + "integrity": "sha512-s6k2b8mmt9gF9pEfkxsaO1lTxaySfKoEJzEfmwguBbQ//Oq23hIXCfR1hm4kdh5hnR20RdwB+s3BCb+0duHSZA==", + "dependencies": { + "iterall": "^1.3.0" + }, + "peerDependencies": { + "graphql": "^15.7.2 || ^16.0.0" + } + }, + "node_modules/graphql-validation-complexity": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/graphql-validation-complexity/-/graphql-validation-complexity-0.4.2.tgz", + "integrity": "sha512-4tzmN/70a06c2JH5fvISkoLX6oBDpqK22cvr2comge3HZHtBLD3n5Sl6MnQYMVhQqKGlpZWcCgD00MnyKNzYYg==", + "dependencies": { + "warning": "^4.0.3" + }, + "peerDependencies": { + "graphql": ">=0.9.5" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ioredis": { + "version": "4.28.3", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.3.tgz", + "integrity": "sha512-9JOWVgBnuSxpIgfpjc1OeY1OLmA4t2KOWWURTDRXky+eWO0LZhI33pQNT9gYxANUXfh5p/zYephYni6GPRsksQ==", + "dependencies": { + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.1", + "denque": "^1.1.0", + "lodash.defaults": "^4.2.0", + "lodash.flatten": "^4.4.0", + "lodash.isarguments": "^3.1.0", + "p-map": "^2.1.0", + "redis-commands": "1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "dev": true, + "dependencies": { + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "dev": true, + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "dev": true, + "dependencies": { + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-cli/node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-config/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-config/node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "dev": true, + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-environment-node/node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "node_modules/jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "dependencies": { + "@jest/types": "30.3.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.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "dev": true, + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-resolve/node_modules/jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "dev": true, + "dependencies": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "dev": true, + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-runtime/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "dev": true, + "dependencies": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sha256": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz", + "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-placeholder-replacer": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", + "integrity": "sha512-edlSWqcFVUpKPshaIcJfXpQ8eu0//gk8iU6XHWkCZIp5QEp4hoCFR7uk+LrIzhLTSqmBQ9VBs+EYK8pvWGEpRg==", + "bin": { + "jpr": "dist/index.js", + "json-placeholder-replacer": "dist/index.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "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" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loglevel": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-plural": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.1.0.tgz", + "integrity": "sha512-PKkwVlAxYVo98NrbclaQIT4F5Oy+X58PZM5r2IwUSCe3syya6PXkIRCn2XCdz7p58Scgpp50PBeHmepXVDG3hg==" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.1.tgz", + "integrity": "sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==", + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/nats": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nats/-/nats-2.18.0.tgz", + "integrity": "sha512-zZF004ejzf67Za0Tva+xphxoxBMNc5IMLqbZ7Ho0j9TMuisjpo+qCd1EktXRCLNxmrZ8O6Tbm1dBsZYNF6yR1A==", + "dependencies": { + "nkeys.js": "1.0.5" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nkeys.js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.0.5.tgz", + "integrity": "sha512-u25YnRPHiGVsNzwyHnn+PT90sgAhnS8jUJ1nxmkHMFYCJ6+Ic0lv291w7uhRBpJVJ3PH2GWbYqA151lGCRrB5g==", + "dependencies": { + "tweetnacl": "1.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/node-environment-flags/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/notation": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/notation/-/notation-1.3.6.tgz", + "integrity": "sha512-DIuJmrP/Gg1DcXKaApsqcjsJD6jEccqKSfmU3BUx/f1GHsMiTJh70cERwYc64tOmTRTARCeMwkqNNzjh3AHhiw==", + "license": "MIT" + }, + "node_modules/notifications-node-client": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-8.2.1.tgz", + "integrity": "sha512-wyZh/NbjN8S2uQX18utYtCyC726BBaGeTc4HeUpdhZv5sYKuaQY94N31v9syh8SzVgehyMzW37y08EePmi+k3Q==", + "dependencies": { + "axios": "^1.7.2", + "jsonwebtoken": "^9.0.2" + }, + "engines": { + "node": ">=14.17.3", + "npm": ">=6.14.13" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-headers": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", + "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pofile": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz", + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==", + "dev": true + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-polyfill": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz", + "integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/pseudolocale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-2.1.0.tgz", + "integrity": "sha512-af5fsrRvVwD+MBasBJvuDChT0KDqT0nEwD9NTgbtHJ16FKomWac9ua0z6YVNB4G9x9IOaiGWym62aby6n4tFMA==", + "dev": true, + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "pseudolocale": "dist/cli.mjs" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pseudolocale/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", + "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", + "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^9.0.0", + "regjsgen": "^0.5.2", + "regjsparser": "^0.7.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", + "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sha.js/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", + "dev": true + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.1.tgz", + "integrity": "sha512-CcRSdb/P2oUVaEpQ87w9Obsl+E9FruRd6b2b7LdiBtJoyMr2DQt7a89anAfiX/EL59j9b2CbRFvf2S91DhuCww==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-slug": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/url-slug/-/url-slug-3.0.2.tgz", + "integrity": "sha512-puioWUGY+esk4kKW8L6fCZWb+xK1+0L/KH2miV6GEJdlCJRJ2lfRlvHkUikyEU1e1v4j1C1HBQKvuljFOxmnEA==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validator": { + "version": "13.15.22", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.22.tgz", + "integrity": "sha512-uT/YQjiyLJP7HSrv/dPZqK9L28xf8hsNca01HSz1dfmI0DgMfjopp1rO/z13NeGF1tVystF0Ejx3y4rUKPw+bQ==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==", + "engines": { + "node": ">=10" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@apollo/cache-control-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", + "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "requires": {} + }, + "@apollo/server": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", + "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", + "requires": { + "@apollo/cache-control-types": "^1.0.3", + "@apollo/server-gateway-interface": "^2.0.0", + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.isnodelike": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0", + "@apollo/utils.usagereporting": "^2.1.0", + "@apollo/utils.withrequired": "^3.0.0", + "@graphql-tools/schema": "^10.0.0", + "async-retry": "^1.2.1", + "body-parser": "^2.2.2", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "finalhandler": "^2.1.0", + "loglevel": "^1.6.8", + "lru-cache": "^11.1.0", + "negotiator": "^1.0.0", + "uuid": "^11.1.0", + "whatwg-mimetype": "^4.0.0" + }, + "dependencies": { + "@apollo/utils.dropunuseddefinitions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz", + "integrity": "sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==", + "requires": {} + }, + "@apollo/utils.printwithreducedwhitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz", + "integrity": "sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==", + "requires": {} + }, + "@apollo/utils.removealiases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz", + "integrity": "sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==", + "requires": {} + }, + "@apollo/utils.sortast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz", + "integrity": "sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==", + "requires": { + "lodash.sortby": "^4.7.0" + } + }, + "@apollo/utils.stripsensitiveliterals": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz", + "integrity": "sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==", + "requires": {} + }, + "@apollo/utils.usagereporting": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz", + "integrity": "sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==", + "requires": { + "@apollo/usage-reporting-protobuf": "^4.1.0", + "@apollo/utils.dropunuseddefinitions": "^2.0.1", + "@apollo/utils.printwithreducedwhitespace": "^2.0.1", + "@apollo/utils.removealiases": "2.0.1", + "@apollo/utils.sortast": "^2.0.1", + "@apollo/utils.stripsensitiveliterals": "^2.0.1" + } + }, + "body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "requires": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + } + }, + "iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==" + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "requires": { + "mime-db": "^1.54.0" + } + }, + "raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + } + }, + "type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "requires": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + } + }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + } + } + }, + "@apollo/server-gateway-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz", + "integrity": "sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==", + "requires": { + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0" + } + }, + "@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "requires": { + "@apollo/protobufjs": "1.2.7" + }, + "dependencies": { + "@apollo/protobufjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", + "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", + "requires": { + "@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/long": "^4.0.0", + "long": "^4.0.0" + } + } + } + }, + "@apollo/utils.createhash": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz", + "integrity": "sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==", + "requires": { + "@apollo/utils.isnodelike": "^3.0.0", + "sha.js": "^2.4.11" + } + }, + "@apollo/utils.fetcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz", + "integrity": "sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==" + }, + "@apollo/utils.isnodelike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz", + "integrity": "sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==" + }, + "@apollo/utils.keyvaluecache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "requires": { + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==" + } + } + }, + "@apollo/utils.logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==" + }, + "@apollo/utils.withrequired": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz", + "integrity": "sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==" + }, + "@as-integrations/express4": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@as-integrations/express4/-/express4-1.1.2.tgz", + "integrity": "sha512-PGeMcwoOKdYnZ4LtsmM7aLNoel3tbK8wKnfyahdRau1qb7wLbuaXB35zg3w34Ov4bm3WJtO3yzd8Bw5jVE+aIQ==", + "requires": {} + }, + "@babel/cli": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz", + "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==", + "dev": true, + "requires": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + } + }, + "@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true + }, + "@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "requires": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.7.tgz", + "integrity": "sha512-kIFozAvVfK05DM4EVQYKK+zteWvY85BFdGBRQBytRyY3y+6PX0DkDOn/CZ3lEuczCfrCxEzwt0YtP/87YPTWSw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz", + "integrity": "sha512-fk5A6ymfp+O5+p2yCkXAu5Kyj6v0xh0RBeNcAkYUMDvvAAoxvSKXn+Jb37t/yWFiQVDFK1ELpUTD8/aLhCPu+g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^4.7.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", + "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" + } + }, + "@babel/helper-replace-supers": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-simple-access": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" + } + }, + "@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "requires": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/node": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.16.8.tgz", + "integrity": "sha512-V2dopEtPUL4LD+e8UtMIZB6BbsmMsS/7E1ZAvWNINzBfi7Cf3X9MLCpzHVZT4HeeF1lQl72IRtqqVt2RUImwyA==", + "dev": true, + "requires": { + "@babel/register": "^7.16.8", + "commander": "^4.0.1", + "core-js": "^3.20.2", + "node-environment-flags": "^1.0.5", + "regenerator-runtime": "^0.13.4", + "v8flags": "^3.1.1" + } + }, + "@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "requires": { + "@babel/types": "^7.29.0" + } + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", + "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", + "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.7" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", + "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", + "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", + "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.7.tgz", + "integrity": "sha512-3O0Y4+dw94HA86qSg9IHfyPktgR7q3gpNVAeiKQd+8jBKFaU5NQS1Yatgo4wY+UFNuLjvxcSmzcsHqrhgTyBUA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.16.7" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.7.tgz", + "integrity": "sha512-7twV3pzhrRxSwHeIvFE6coPgvo+exNDOiGUMg39o2LiLo1Y+4aKpfkcLGcg1UHonzorCt7SNXnoMyCnnIOA8Sw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", + "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.28.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", + "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", + "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", + "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", + "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", + "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", + "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", + "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", + "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", + "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", + "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", + "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", + "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", + "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", + "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", + "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", + "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", + "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", + "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", + "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", + "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/preset-env": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.8.tgz", + "integrity": "sha512-9rNKgVCdwHb3z1IlbMyft6yIXIeP3xz6vWvGaLHrJThuEIqWfHb0DNBH9VuTgnDfdbUDhkmkvMZS/YMCtP7Elg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-async-generator-functions": "^7.16.8", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.16.7", + "@babel/plugin-proposal-json-strings": "^7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-private-methods": "^7.16.7", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@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/plugin-transform-arrow-functions": "^7.16.7", + "@babel/plugin-transform-async-to-generator": "^7.16.8", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.16.7", + "@babel/plugin-transform-classes": "^7.16.7", + "@babel/plugin-transform-computed-properties": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.16.7", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.16.7", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.16.7", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.16.8", + "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-umd": "^7.16.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-new-target": "^7.16.7", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.16.7", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-reserved-words": "^7.16.7", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.16.7", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.16.7", + "@babel/plugin-transform-typeof-symbol": "^7.16.7", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.8", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.20.2", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/register": { + "version": "7.16.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.16.9.tgz", + "integrity": "sha512-jJ72wcghdRIlENfvALcyODhNoGE5j75cYHdC+aQMh6cU/P86tiiXTp9XYZct1UxUMo/4+BgQRyNZEGx0KWGS+g==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.0", + "source-map-support": "^0.5.16" + } + }, + "@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "requires": { + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } + } + }, + "@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + } + }, + "@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + } + }, + "@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true + }, + "@graphql-tools/merge": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.6.tgz", + "integrity": "sha512-bTnP+4oom4nDjmkS3Ykbe+ljAp/RIiWP3R35COMmuucS24iQxGLa9Hn8VMkLIoaoPxgz6xk+dbC43jtkNsFoBw==", + "requires": { + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + } + }, + "@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "requires": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + } + }, + "@graphql-tools/utils": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", + "requires": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + } + }, + "@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "requires": {} + }, + "@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "optional": true + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "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": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "requires": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@jest/core": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "requires": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true + }, + "@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "requires": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + } + }, + "@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "requires": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + } + }, + "@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0" + } + }, + "@jest/fake-timers": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "requires": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + } + }, + "@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true + }, + "@jest/globals": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "requires": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" + } + }, + "@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + } + }, + "@jest/reporters": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "requires": { + "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" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.2" + } + }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "requires": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + } + }, + "@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + } + }, + "@jest/test-result": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "requires": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + } + }, + "@jest/test-sequencer": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "requires": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@jest/transform": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "requires": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.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.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@jest/types": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@lingui/babel-plugin-extract-messages": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-5.4.1.tgz", + "integrity": "sha512-sjkVaLyuK3ZW62mv5gU6pOdl3ZpwDReeSaNodJuf9LssbMIQPa5WOirTnMeBaalrQ8BA2srrRzQAWgsXPQVdXA==", + "dev": true + }, + "@lingui/babel-plugin-lingui-macro": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-5.4.1.tgz", + "integrity": "sha512-9IO+PDvdneY8OCI8zvI1oDXpzryTMtyRv7uq9O0U1mFCvIPVd5dWQKQDu/CpgpYAc2+JG/izn5PNl9xzPc6ckw==", + "dev": true, + "requires": { + "@babel/core": "^7.20.12", + "@babel/runtime": "^7.20.13", + "@babel/types": "^7.20.7", + "@lingui/conf": "5.4.1", + "@lingui/core": "5.4.1", + "@lingui/message-utils": "5.4.1" + }, + "dependencies": { + "@lingui/core": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-5.4.1.tgz", + "integrity": "sha512-4FeIh56PH5vziPg2BYo4XYWWOHE4XaY/XR8Jakwn0/qwtLpydWMNVpZOpGWi7nfPZtcLaJLmZKup6UNxEl1Pfw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.4.1" + } + }, + "@lingui/message-utils": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.4.1.tgz", + "integrity": "sha512-hXfL90fFBoKp5YgLaWo3HbJS/7q+WlWs7VwVbUxl4pa+YladqNZf08JoDeBUDtlEVx5a3bNUSACXHs2FZo12aw==", + "dev": true, + "requires": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + } + } + } + }, + "@lingui/cli": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-5.4.1.tgz", + "integrity": "sha512-UAKA9Iz4zMDJS7fzWMZ4hzQWontrTBnI5XCsPm7ttB0Ed0F4Pwph/Vu7pg4bJdiYr4d6nqEpRWd9aTxcC15/IA==", + "dev": true, + "requires": { + "@babel/core": "^7.21.0", + "@babel/generator": "^7.21.1", + "@babel/parser": "^7.22.0", + "@babel/runtime": "^7.21.0", + "@babel/types": "^7.21.2", + "@lingui/babel-plugin-extract-messages": "5.4.1", + "@lingui/babel-plugin-lingui-macro": "5.4.1", + "@lingui/conf": "5.4.1", + "@lingui/core": "5.4.1", + "@lingui/format-po": "5.4.1", + "@lingui/message-utils": "5.4.1", + "chokidar": "3.5.1", + "cli-table": "^0.3.11", + "commander": "^10.0.0", + "convert-source-map": "^2.0.0", + "date-fns": "^3.6.0", + "esbuild": "^0.25.1", + "glob": "^11.0.0", + "micromatch": "^4.0.7", + "normalize-path": "^3.0.0", + "ora": "^5.1.0", + "picocolors": "^1.1.1", + "pofile": "^1.1.4", + "pseudolocale": "^2.0.0", + "source-map": "^0.8.0-beta.0" + }, + "dependencies": { + "@lingui/core": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-5.4.1.tgz", + "integrity": "sha512-4FeIh56PH5vziPg2BYo4XYWWOHE4XaY/XR8Jakwn0/qwtLpydWMNVpZOpGWi7nfPZtcLaJLmZKup6UNxEl1Pfw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.4.1" + } + }, + "@lingui/message-utils": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.4.1.tgz", + "integrity": "sha512-hXfL90fFBoKp5YgLaWo3HbJS/7q+WlWs7VwVbUxl4pa+YladqNZf08JoDeBUDtlEVx5a3bNUSACXHs2FZo12aw==", + "dev": true, + "requires": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + } + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "requires": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + } + }, + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "requires": { + "whatwg-url": "^7.0.0" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "@lingui/conf": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-5.4.1.tgz", + "integrity": "sha512-aDkj/bMSr/mCL8Nr1TS52v0GLCuVa4YqtRz+WvUCFZw/ovVInX0hKq1TClx/bSlhu60FzB/CbclxFMBw8aLVUg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.13", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^1.17.1", + "picocolors": "^1.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "@lingui/core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-4.13.0.tgz", + "integrity": "sha512-kRqQWeEVoqNrDtEdyHPWGsAHRStN8ObYc5a1gdyuBhoj1zaoUS/DMK5C7B1ZeTtj6rCCmZRs6d2tN12hsZ2zJA==", + "requires": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "4.13.0", + "unraw": "^3.0.0" + } + }, + "@lingui/format-po": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-5.4.1.tgz", + "integrity": "sha512-IBVq3RRLNEVRzNZcdEw0qpM5NKX4e9wDmvJMorkR2OYrgTbhWx5gDYhXpEZ9yqtuEVhILMdriVNjAAUnDAJibA==", + "dev": true, + "requires": { + "@lingui/conf": "5.4.1", + "@lingui/message-utils": "5.4.1", + "date-fns": "^3.6.0", + "pofile": "^1.1.4" + }, + "dependencies": { + "@lingui/message-utils": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.4.1.tgz", + "integrity": "sha512-hXfL90fFBoKp5YgLaWo3HbJS/7q+WlWs7VwVbUxl4pa+YladqNZf08JoDeBUDtlEVx5a3bNUSACXHs2FZo12aw==", + "dev": true, + "requires": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + } + } + } + }, + "@lingui/macro": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-4.13.0.tgz", + "integrity": "sha512-OjhaWOWbTCXHOOHNaGI0shMP3qrPjNZ19tpEx/iStAmJq64fkevx/HbDPI0uuqLX8v1NFWG/SzBMIQzJb5YOvA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.13", + "@babel/types": "^7.20.7", + "@lingui/conf": "4.13.0", + "@lingui/core": "4.13.0", + "@lingui/message-utils": "4.13.0" + }, + "dependencies": { + "@lingui/conf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-4.13.0.tgz", + "integrity": "sha512-7NSinlzgSMKBezLsSM7DMwr0IpTHKr8nuSDpTZpI79+BhW+Xq38jPRQqMXdzItW8Cl/Lsdr3Y3MnYJIl8tADsQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.13", + "chalk": "^4.1.0", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^1.17.1", + "lodash.get": "^4.4.2" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "@lingui/message-utils": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-4.13.0.tgz", + "integrity": "sha512-tI/WBVZym+APwqk0O3xTaF0k+RQIv5E4PqGHdXqwbofycHly2C+izH+hg6UeNctc6jd19GRwqu/4ga9knkdAlQ==", + "requires": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + } + }, + "@lingui/react": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-4.13.0.tgz", + "integrity": "sha512-5remR9rVwosiiX/RnEWETHA8cpqQiP7U87OXXMPz67LuyG3XP8RP+ic75rVn284DHLHgpjDbauz7vYIz855ZoQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.20.13", + "@lingui/core": "4.13.0" + } + }, + "@messageformat/parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz", + "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==", + "requires": { + "moo": "^0.5.1" + } + }, + "@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "requires": { + "@noble/hashes": "^1.1.5" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.1" + } + }, + "@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "requires": { + "undici-types": "~6.21.0" + } + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "@typescript-eslint/project-service": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "dev": true, + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" + } + }, + "@typescript-eslint/scope-manager": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" + } + }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "dev": true, + "requires": {} + }, + "@typescript-eslint/types": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "dev": true, + "requires": { + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + }, + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true + } + } + }, + "@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "dev": true, + "optional": true, + "requires": { + "@napi-rs/wasm-runtime": "^0.2.11" + } + }, + "@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "dev": true, + "optional": true + }, + "@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "dev": true, + "optional": true + }, + "@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "requires": { + "tslib": "^2.6.3" + } + }, + "accept-language": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", + "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=", + "requires": { + "bcp47": "^1.1.2", + "stable": "^0.1.6" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "dependencies": { + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + } + } + }, + "accesscontrol": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/accesscontrol/-/accesscontrol-2.2.1.tgz", + "integrity": "sha512-52EvFk/J9EF+w4mYQoKnOTkEMj01R1U5n2fc1dai6x1xkgOks3DGkx01qQL2cKFxGmE4Tn1krAU3jJA9L1NMkg==", + "requires": { + "notation": "^1.3.6" + } + }, + "acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "requires": { + "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-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arango-tools": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", + "integrity": "sha512-2XjPPddz7Vc07JrOyXFYGzXI+QZOAR+I0kiyklKBevWVTTDh1lYRetZh3X1fN+smibgL2DY3jKwHK14aSzVkSw==", + "requires": { + "arangojs": "^7.2.0", + "assign-deep": "^1.0.1", + "json-placeholder-replacer": "^1.0.35" + }, + "dependencies": { + "arangojs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.8.0.tgz", + "integrity": "sha512-aJFlMKlVr4sIO5GDMuykBVNVxWeZTkWDgYbbl9cIuxVctp8Lhs6dW5fr5MYlwAndnOEyi3bvbrhZIucly2IpWQ==", + "requires": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + } + }, + "file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==" + }, + "mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "requires": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + } + }, + "multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "requires": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + } + } + } + }, + "arangojs": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-10.2.2.tgz", + "integrity": "sha512-3Xllq5inTGjros0mBP9NFxrIW8Di0ldtFurLdrXy5z4NDVJPyJtnwUiiGrMPY21NuVu53wUDE23YN50jnX4epw==", + "requires": { + "@types/node": "^20.11.26" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "requires": { + "assign-symbols": "^2.0.2" + } + }, + "assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" + }, + "async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "requires": { + "retry": "0.13.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "requires": { + "follow-redirects": "^1.15.11", + "form-data": "4.0.4", + "proxy-from-env": "^2.1.0" + } + }, + "babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "requires": {} + }, + "babel-jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "dev": true, + "requires": { + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "dependencies": { + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "requires": { + "@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": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "dev": true, + "requires": { + "@types/babel__core": "^7.20.5" + } + }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", + "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.0", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.1.tgz", + "integrity": "sha512-TihqEe4sQcb/QcPJvxe94/9RZuLQuF1+To4WqQcRvc+3J3gLCPIPgDKzGLG6zmQLfH3nn25heRuDNkS2KR4I8A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.20.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz", + "integrity": "sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.0" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "requires": { + "@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": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcp47": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", + "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=" + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "requires": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, + "builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "requires": { + "semver": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + } + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "requires": { + "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-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001675", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001675.tgz", + "integrity": "sha512-/wV1bQwPrkLiQMjaJF5yUMVM/VdRPOCU8QZ+PmG6uW6DvYSrNY1bpwHI/3mOcUosLaJCzYDi5o91IQB51ft6cg==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + } + } + }, + "ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true + }, + "cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", + "dev": true + }, + "cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "requires": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "requires": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, + "core-js": { + "version": "3.20.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", + "integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==", + "dev": true + }, + "core-js-compat": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "dev": true, + "requires": { + "browserslist": "^4.22.2" + } + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, + "cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "requires": { + "tslib": "^2.4.0" + } + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "dataloader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", + "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" + }, + "date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "dev": true + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "requires": {} + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, + "dotenv-safe": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", + "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", + "requires": { + "dotenv": "^8.2.0" + } + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "electron-to-chromium": { + "version": "1.5.49", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz", + "integrity": "sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "requires": { + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "requires": {} + }, + "eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz", + "integrity": "sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + } + }, + "eslint-plugin-import": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.2", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.12.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-jest": { + "version": "29.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.1.tgz", + "integrity": "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^8.0.0" + } + }, + "eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, + "dependencies": { + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "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-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true + }, + "expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, + "requires": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + } + }, + "express": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.0.tgz", + "integrity": "sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==", + "requires": { + "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.14.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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "express-request-language": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/express-request-language/-/express-request-language-1.1.15.tgz", + "integrity": "sha512-KiLUdEZCcgwh8qfIvkCrhz1MMAFx/Xj4UcspN4zUxVdp+bp+yFvqUMmlyMHK2nC5JlQV7VK5uFOoS5LrArTL1A==", + "requires": { + "accept-language": "^3.0.4", + "bcp47": "^1.1.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "requires": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==" + }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "requires": { + "is-callable": "^1.2.7" + } + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, + "form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "requires": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "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": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "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-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==" + }, + "graphql-depth-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz", + "integrity": "sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw==", + "requires": { + "arrify": "^1.0.1" + } + }, + "graphql-redis-subscriptions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.6.0.tgz", + "integrity": "sha512-hWAPkCNSFKpEOJBMLAcbpGl/gv+s1Yho2JIwP9MK2RlvqlWiqhAYqjA/HVRK86nkYsRkBwGgqvaNxtV1fAexBQ==", + "requires": { + "ioredis": "^5.2.4" + }, + "dependencies": { + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "optional": true + }, + "ioredis": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", + "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "optional": true, + "requires": { + "@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" + } + } + } + }, + "graphql-relay": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.2.tgz", + "integrity": "sha512-abybva1hmlNt7Y9pMpAzHuFnM2Mme/a2Usd8S4X27fNteLGRAECMYfhmsrpZFvGn3BhmBZugMXYW/Mesv3P1Kw==", + "requires": {} + }, + "graphql-scalars": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.25.0.tgz", + "integrity": "sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg==", + "requires": { + "tslib": "^2.5.0" + } + }, + "graphql-subscriptions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz", + "integrity": "sha512-s6k2b8mmt9gF9pEfkxsaO1lTxaySfKoEJzEfmwguBbQ//Oq23hIXCfR1hm4kdh5hnR20RdwB+s3BCb+0duHSZA==", + "requires": { + "iterall": "^1.3.0" + } + }, + "graphql-validation-complexity": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/graphql-validation-complexity/-/graphql-validation-complexity-0.4.2.tgz", + "integrity": "sha512-4tzmN/70a06c2JH5fvISkoLX6oBDpqK22cvr2comge3HZHtBLD3n5Sl6MnQYMVhQqKGlpZWcCgD00MnyKNzYYg==", + "requires": { + "warning": "^4.0.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "ioredis": { + "version": "4.28.3", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.3.tgz", + "integrity": "sha512-9JOWVgBnuSxpIgfpjc1OeY1OLmA4t2KOWWURTDRXky+eWO0LZhI33pQNT9gYxANUXfh5p/zYephYni6GPRsksQ==", + "requires": { + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.1", + "denque": "^1.1.0", + "lodash.defaults": "^4.2.0", + "lodash.flatten": "^4.4.0", + "lodash.isarguments": "^3.1.0", + "p-map": "^2.1.0", + "redis-commands": "1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "requires": { + "which-typed-array": "^1.1.16" + } + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "requires": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + } + }, + "istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" + }, + "jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2" + } + }, + "jest": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "dev": true, + "requires": { + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" + } + }, + "jest-changed-files": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "dev": true, + "requires": { + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^3.1.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "jest-circus": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "dev": true, + "requires": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "p-limit": "^3.1.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "jest-cli": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "dev": true, + "requires": { + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + } + } + } + }, + "jest-config": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "dev": true, + "requires": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "parse-json": "^5.2.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "requires": { + "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" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.2" + } + }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "requires": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + } + }, + "jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "requires": { + "detect-newline": "^3.1.0" + } + }, + "jest-each": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" + } + }, + "jest-environment-node": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "dev": true, + "requires": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + } + } + } + }, + "jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "requires": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "jest-haste-map": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "dev": true, + "requires": { + "@jest/types": "30.3.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "fsevents": "^2.3.3", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "dependencies": { + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, + "jest-leak-detector": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" + } + }, + "jest-matcher-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" + } + }, + "jest-message-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "dependencies": { + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "jest-mock": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, + "requires": { + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true + }, + "jest-resolve": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "jest-validate": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "dev": true, + "requires": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.3.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "jest-resolve-dependencies": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "dev": true, + "requires": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" + } + }, + "jest-runner": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "dev": true, + "requires": { + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "dev": true, + "requires": { + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "requires": { + "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" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.2" + } + }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "jest-snapshot": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "dev": true, + "requires": { + "@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.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "dependencies": { + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + } + } + }, + "jest-util": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, + "requires": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "dependencies": { + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "requires": { + "@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" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + } + } + }, + "jest-watcher": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "dev": true, + "requires": { + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.3.0", + "string-length": "^4.0.2" + } + }, + "jest-worker": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true + }, + "js-sha256": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz", + "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-placeholder-replacer": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", + "integrity": "sha512-edlSWqcFVUpKPshaIcJfXpQ8eu0//gk8iU6XHWkCZIp5QEp4hoCFR7uk+LrIzhLTSqmBQ9VBs+EYK8pvWGEpRg==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "requires": { + "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" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "requires": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "requires": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "loglevel": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } + } + }, + "make-plural": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.1.0.tgz", + "integrity": "sha512-PKkwVlAxYVo98NrbclaQIT4F5Oy+X58PZM5r2IwUSCe3syya6PXkIRCn2XCdz7p58Scgpp50PBeHmepXVDG3hg==" + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "min-document": { + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.1.tgz", + "integrity": "sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g==", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==" + }, + "napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true + }, + "nats": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nats/-/nats-2.18.0.tgz", + "integrity": "sha512-zZF004ejzf67Za0Tva+xphxoxBMNc5IMLqbZ7Ho0j9TMuisjpo+qCd1EktXRCLNxmrZ8O6Tbm1dBsZYNF6yR1A==", + "requires": { + "nkeys.js": "1.0.5" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" + }, + "nkeys.js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.0.5.tgz", + "integrity": "sha512-u25YnRPHiGVsNzwyHnn+PT90sgAhnS8jUJ1nxmkHMFYCJ6+Ic0lv291w7uhRBpJVJ3PH2GWbYqA151lGCRrB5g==", + "requires": { + "tweetnacl": "1.0.3" + } + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "notation": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/notation/-/notation-1.3.6.tgz", + "integrity": "sha512-DIuJmrP/Gg1DcXKaApsqcjsJD6jEccqKSfmU3BUx/f1GHsMiTJh70cERwYc64tOmTRTARCeMwkqNNzjh3AHhiw==" + }, + "notifications-node-client": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-8.2.1.tgz", + "integrity": "sha512-wyZh/NbjN8S2uQX18utYtCyC726BBaGeTc4HeUpdhZv5sYKuaQY94N31v9syh8SzVgehyMzW37y08EePmi+k3Q==", + "requires": { + "axios": "^1.7.2", + "jsonwebtoken": "^9.0.2" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "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": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "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" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-headers": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", + "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==" + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@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" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "dependencies": { + "lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true + } + } + }, + "path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "pofile": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz", + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==", + "dev": true + }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, + "pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "requires": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "promise-polyfill": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz", + "integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==" + }, + "pseudolocale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-2.1.0.tgz", + "integrity": "sha512-af5fsrRvVwD+MBasBJvuDChT0KDqT0nEwD9NTgbtHJ16FKomWac9ua0z6YVNB4G9x9IOaiGWym62aby6n4tFMA==", + "dev": true, + "requires": { + "commander": "^10.0.0" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + } + } + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true + }, + "qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "requires": { + "side-channel": "^1.1.0" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + } + }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "peer": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "requires": { + "redis-errors": "^1.0.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", + "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpu-core": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", + "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^9.0.0", + "regjsgen": "^0.5.2", + "regjsparser": "^0.7.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", + "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "requires": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + } + } + }, + "serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "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": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "requires": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "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" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "requires": { + "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" + } + }, + "signal-exit": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", + "dev": true + }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true + } + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "superagent": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.1.tgz", + "integrity": "sha512-CcRSdb/P2oUVaEpQ87w9Obsl+E9FruRd6b2b7LdiBtJoyMr2DQt7a89anAfiX/EL59j9b2CbRFvf2S91DhuCww==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "4.0.4", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "dependencies": { + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "requires": { + "@pkgr/core": "^0.2.9" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "requires": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true + }, + "ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "requires": {} + }, + "tsconfig-paths": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, + "typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "peer": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" + }, + "unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "requires": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1", + "napi-postinstall": "^0.3.0" + } + }, + "update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-slug": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/url-slug/-/url-slug-3.0.2.tgz", + "integrity": "sha512-puioWUGY+esk4kKW8L6fCZWb+xK1+0L/KH2miV6GEJdlCJRJ2lfRlvHkUikyEU1e1v4j1C1HBQKvuljFOxmnEA==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } + } + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validator": { + "version": "13.15.22", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.22.tgz", + "integrity": "sha512-uT/YQjiyLJP7HSrv/dPZqK9L28xf8hsNca01HSz1dfmI0DgMfjopp1rO/z13NeGF1tVystF0Ejx3y4rUKPw+bQ==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "requires": { + "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" + } + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, + "x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==" + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "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" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000000..3e59de346f --- /dev/null +++ b/api/package.json @@ -0,0 +1,90 @@ +{ + "name": "tracker-api", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/canada-ca/tracker/tree/master/api" + }, + "author": "nsdeschenes", + "license": "MIT", + "scripts": { + "build": "npm run clean && babel ./src --out-dir dist/src --ignore 'src/**/*.spec.js','src/**/*.test.js' && babel index.js --out-dir dist/", + "start": "node ./dist/index.js", + "dev": "NODE_OPTIONS=--dns-result-order=ipv4first babel-node index.js", + "dev:watch": "nodemon --exec babel-node index.js", + "clean": "rm -rf ./dist && mkdir dist", + "test": "NODE_OPTIONS=--dns-result-order=ipv4first jest --testPathIgnorePatterns=.*-scan-data.* --env=node", + "only": "NODE_OPTIONS=--dns-result-order=ipv4first jest", + "test-coverage": "jest --coverage", + "lint": "eslint src", + "prettier": "prettier --write \"**/*.js\"", + "extract": "npx lingui extract", + "compile": "npx lingui compile", + "lin-clean": "npx lingui extract --clean" + }, + "overrides": { + "form-data@^3.0.0": "3.0.4", + "form-data@^4.0.0": "4.0.4" + }, + "dependencies": { + "@apollo/server": "^5.5.0", + "@as-integrations/express4": "^1.1.2", + "@lingui/core": "^4.13.0", + "accesscontrol": "^2.2.1", + "arango-tools": "^0.6.0", + "arangojs": "^10.2.2", + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.4", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dataloader": "^2.0.0", + "dotenv-safe": "^8.2.0", + "express": "^4.22.0", + "express-request-language": "^1.1.15", + "graphql": "^16.12.0", + "graphql-depth-limit": "^1.1.0", + "graphql-redis-subscriptions": "^2.6.0", + "graphql-relay": "^0.10.2", + "graphql-scalars": "^1.25.0", + "graphql-subscriptions": "^2.0.0", + "graphql-validation-complexity": "^0.4.2", + "ioredis": "^4.28.3", + "isomorphic-fetch": "^3.0.0", + "jsonwebtoken": "^9.0.2", + "make-plural": "^7.1.0", + "moment": "^2.29.4", + "ms": "^2.1.3", + "nats": "^2.18.0", + "notifications-node-client": "^8.2.1", + "url-slug": "^3.0.2", + "uuid": "^8.3.2", + "validator": "^13.15.22" + }, + "devDependencies": { + "@babel/cli": "^7.16.8", + "@babel/core": "^7.16.7", + "@babel/node": "^7.16.8", + "@babel/preset-env": "^7.16.8", + "@jest/test-sequencer": "^30.3.0", + "@lingui/cli": "^5.4.1", + "@lingui/macro": "^4.13.0", + "babel-core": "^7.0.0-bridge.0", + "babel-jest": "^30.3.0", + "babel-plugin-macros": "^3.1.0", + "babel-polyfill": "^6.26.0", + "eslint": "^8.57.1", + "eslint-config-prettier": "^8.3.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^29.15.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.6.0", + "jest": "^30.3.0", + "jest-fetch-mock": "^3.0.3", + "jest-matcher-utils": "^30.3.0", + "nodemon": "^3.1.11", + "prettier": "^2.5.1", + "supertest": "^7.0.0" + } +} diff --git a/api/src/__tests__/create-context.test.js b/api/src/__tests__/create-context.test.js new file mode 100644 index 0000000000..d4d9b39be5 --- /dev/null +++ b/api/src/__tests__/create-context.test.js @@ -0,0 +1,37 @@ +const {createContext} = require('../create-context.js') +const {tokenize} = require('../auth') + +describe('given the create context function', () => { + describe('request authorization token is not set', () => { + it('returns object with userKey as "NO_USER"', async () => { + const context = await createContext({ + query: jest.fn(), + transaction: jest.fn(), + collections: [], + req: {headers: {}, language: 'en'}, + res: {}, + }) + + expect(context.userKey).toEqual('NO_USER') + }) + }) + + describe('request authorization token is set', () => { + it('returns object with userKey as value', async () => { + const context = await createContext({ + query: jest.fn(), + transaction: jest.fn(), + collections: [], + req: { + language: 'en', + headers: { + authorization: tokenize({parameters: {userKey: '1234'}}), + }, + }, + res: {}, + }) + + expect(context.userKey).toEqual('1234') + }) + }) +}) diff --git a/api/src/__tests__/initialize-loaders.test.js b/api/src/__tests__/initialize-loaders.test.js new file mode 100644 index 0000000000..923f4ab5f7 --- /dev/null +++ b/api/src/__tests__/initialize-loaders.test.js @@ -0,0 +1,45 @@ +import { initializeLoaders } from '../initialize-loaders' + +describe('initializeLoaders', () => { + it('returns a object with a key for each loader', () => { + const loaders = initializeLoaders({ + query: jest.fn(), + userKey: '1234', + i18n: jest.fn(), + language: 'en', + cleanseInput: jest.fn(), + loginRequiredBool: true, + moment: jest.fn(), // momentjs + }) + + expect(loaders).toHaveProperty( + 'loadDkimFailConnectionsBySumId', + 'loadDmarcFailConnectionsBySumId', + 'loadDmarcSummaryConnectionsByUserId', + 'loadDmarcSummaryEdgeByDomainIdAndPeriod', + 'loadDmarcSummaryByKey', + 'loadFullPassConnectionsBySumId', + 'loadSpfFailureConnectionsBySumId', + 'loadStartDateFromPeriod', + 'loadDmarcYearlySumEdge', + 'loadDomainByDomain', + 'loadDomainByKey', + 'loadDomainConnectionsByOrgId', + 'loadDomainConnectionsByUserId', + 'loadOrgByKey', + 'loadUserByUserName', + 'loadUserByKey', + 'loadAffiliationByKey', + 'loadAffiliationConnectionsByUserId', + 'loadAffiliationConnectionsByOrgId', + 'loadVerifiedDomainsById', + 'loadVerifiedDomainByKey', + 'loadVerifiedDomainConnections', + 'loadVerifiedDomainConnectionsByOrgId', + 'loadVerifiedOrgByKey', + 'loadVerifiedOrgBySlug', + 'loadVerifiedOrgConnectionsByDomainId', + 'loadVerifiedOrgConnections', + ) + }) +}) diff --git a/api/src/__tests__/server.test.js b/api/src/__tests__/server.test.js new file mode 100644 index 0000000000..94f16e71d2 --- /dev/null +++ b/api/src/__tests__/server.test.js @@ -0,0 +1,145 @@ +import request from 'supertest' +import { Server } from '../server' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../testUtilities' +import dbschema from '../../database.json' + +const { + DB_PASS: rootPass, + DB_URL: url, + DEPTH_LIMIT: maxDepth, + COST_LIMIT: complexityCost, + SCALAR_COST: scalarCost, + OBJECT_COST: objectCost, + LIST_FACTOR: listFactor, +} = process.env + +const name = dbNameFromFile(__filename) +let drop +describe('parse server', () => { + const consoleOutput = [] + const mockedLog = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.log = mockedLog + console.warn = mockedWarn + // create the database so that middleware can connect + ;({ drop } = await ensure({ + variables: { + dbname: name, + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + + afterAll(() => drop()) + + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('/alive', () => { + it('returns 200', async () => { + const server = await Server({}) + + const response = await request(server).get('/alive') + expect(response.status).toEqual(200) + }) + }) + + describe('/ready', () => { + it('returns 200', async () => { + const server = await Server({ + arango: { + db: name, + url, + as: { + username: 'root', + password: rootPass, + }, + }, + }) + + const response = await request(server).get('/ready') + expect(response.status).toEqual(200) + }) + }) + + describe('/graphql', () => { + describe('endpoint is alive', () => { + it('returns 200', async () => { + const response = await request( + await Server({ + maxDepth, + complexityCost, + scalarCost, + objectCost, + listFactor, + tracing: false, + context: () => { + return { query: jest.fn(), collections: jest.fn(), transaction: jest.fn() } + }, + }), + ) + .post('/graphql') + .set('Accept', 'application/json') + .send({ query: '{__schema {types {kind}}}' }) + + expect(response.status).toEqual(200) + }) + }) + describe('validation rule is broken', () => { + describe('query cost is too high', () => { + it('returns an error message', async () => { + const response = await request( + await Server({ + maxDepth: 5, + complexityCost: 1, + scalarCost: 100, + objectCost: 100, + listFactor: 100, + context: () => { + return { query: jest.fn(), collections: jest.fn(), transaction: jest.fn() } + }, + }), + ) + .post('/graphql') + .set('Accept', 'application/json') + .send({ query: '{__schema {types {kind}}}' }) + + expect(response.status).toEqual(400) + expect(response.text).toEqual(expect.stringContaining('Query error, query is too complex.')) + }) + }) + + describe('query depth is too high', () => { + it('returns an error message', async () => { + const response = await request( + await Server({ + maxDepth: 1, + complexityCost: 1000, + scalarCost: 1, + objectCost: 1, + listFactor: 1, + context: () => { + return { query: jest.fn(), collections: jest.fn(), transaction: jest.fn() } + }, + }), + ) + .post('/graphql') + .set('Accept', 'application/json') + .send({ + query: '{findVerifiedDomains (first: 5) { edges { node { id }}}}', + }) + + expect(response.status).toEqual(400) + expect(response.text).toEqual(expect.stringContaining('exceeds maximum operation depth')) + }) + }) + }) + }) +}) diff --git a/api/src/access-control.js b/api/src/access-control.js new file mode 100644 index 0000000000..195bf5633f --- /dev/null +++ b/api/src/access-control.js @@ -0,0 +1,51 @@ +// Central RBAC definition using accesscontrol +// +// This file defines role-based access control (RBAC) permissions using the AccessControl library. +// +// "Own" refers to resources affiliated with the user's organization. For example, an admin can create domains +// in their affiliated organization, but not in other organizations. This ensures users only manage resources +// within their scope of affiliation. +// +// Roles: +// - user: Basic permissions for managing own CSVs, affiliations, scan requests, and viewing own organization. +// - admin: Extends user. Can manage domains, organizations, logs, tags, and affiliations within their own org. +// - owner: Extends admin. Can delete own organization and create domains. +// - super_admin: Extends owner. Can manage any resource across all organizations. +// +// For maintainability, update permissions here when adding new roles or resources. + +import AccessControl from 'accesscontrol' + +const ac = new AccessControl() + +ac.grant('user').createOwn('csv').readOwn('affiliation').createOwn('scan-request').readOwn('organization') + +ac.grant('admin') + .extend('user') + .createOwn('domain') + .updateOwn('domain', ['*', '!archived']) + .deleteOwn('domain') + .updateOwn('organization', ['*', '!externalId', '!externallyManaged']) + .readOwn('log') + .createOwn('tag') + .updateOwn('tag') + .createOwn('affiliation') + .updateOwn('affiliation') + .deleteOwn('affiliation') + +ac.grant('owner').extend('admin').deleteOwn('organization').createOwn('cvd-enrollment').updateOwn('cvd-enrollment') + +ac.grant('super_admin') + .extend('owner') + .createAny(['organization', 'domain', 'user', 'tag', 'affiliation', 'csv', 'scan-request', 'cvd-enrollment']) + .readAny(['organization', 'domain', 'user', 'tag', 'log', 'affiliation']) + .updateAny(['organization', 'domain', 'user', 'tag', 'affiliation', 'cvd-enrollment']) + .deleteAny(['organization', 'domain', 'user', 'tag', 'affiliation']) + +ac.grant('none') // no permissions — fallback for users with no org affiliation +const _can = ac.can.bind(ac) +ac.can = (role) => _can(role || 'none') + +ac.lock() + +export default ac diff --git a/api/src/additional-findings/data-source.js b/api/src/additional-findings/data-source.js new file mode 100644 index 0000000000..e466b46191 --- /dev/null +++ b/api/src/additional-findings/data-source.js @@ -0,0 +1,8 @@ +import { loadAdditionalFindingsByDomainId, loadTop25Reports } from './loaders' + +export class AdditionalFindingsDataSource { + constructor({ query, userKey, i18n, language }) { + this.getByDomainId = loadAdditionalFindingsByDomainId({ query, userKey, i18n }) + this.getTop25Reports = loadTop25Reports({ query, userKey, i18n, language }) + } +} diff --git a/api/src/additional-findings/index.js b/api/src/additional-findings/index.js new file mode 100644 index 0000000000..efc2b38f26 --- /dev/null +++ b/api/src/additional-findings/index.js @@ -0,0 +1,4 @@ +export * from './data-source' +export * from './loaders' +export * from './objects' +export * from './input' diff --git a/api/src/additional-findings/input/cvd-enrollment-options.js b/api/src/additional-findings/input/cvd-enrollment-options.js new file mode 100644 index 0000000000..6f49454873 --- /dev/null +++ b/api/src/additional-findings/input/cvd-enrollment-options.js @@ -0,0 +1,9 @@ +import { GraphQLInputObjectType } from 'graphql' +import { cvdEnrollmentFields } from '../objects/cvd-enrollment' + +export const CvdEnrollmentInputOptions = new GraphQLInputObjectType({ + name: 'CvdEnrollmenInputOptions', + description: + 'Input options for specifying CVD enrollment details, including program status and CVSS environmental requirements.', + fields: () => ({ ...cvdEnrollmentFields }), +}) diff --git a/api/src/additional-findings/input/index.js b/api/src/additional-findings/input/index.js new file mode 100644 index 0000000000..6c8ee05330 --- /dev/null +++ b/api/src/additional-findings/input/index.js @@ -0,0 +1 @@ +export * from './cvd-enrollment-options' diff --git a/api/src/additional-findings/loaders/__tests__/load-additional-findings-by-domain-id.test.js b/api/src/additional-findings/loaders/__tests__/load-additional-findings-by-domain-id.test.js new file mode 100644 index 0000000000..7e86d2495e --- /dev/null +++ b/api/src/additional-findings/loaders/__tests__/load-additional-findings-by-domain-id.test.js @@ -0,0 +1,69 @@ +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +const { setupI18n } = require('@lingui/core') + +describe('loadAdditionalFindingsByDomainId', () => { + const i18n = setupI18n({ + locale: 'en', + localeData: { + en: {}, + fr: {}, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + + it('throws an error when domainId is not provided', async () => { + const loadAdditionalFindingsByDomainId = + require('../load-additional-findings-by-domain-id').loadAdditionalFindingsByDomainId + const func = loadAdditionalFindingsByDomainId({ query: jest.fn(), userKey: 'userKey', i18n }) + + await expect(func({})).rejects.toThrow("You must provide a `domainId` to retrieve a domain's additional findings.") + }) + + it('throws an error when a database error occurs', async () => { + const query = jest.fn(() => { + throw new Error() + }) + const loadAdditionalFindingsByDomainId = + require('../load-additional-findings-by-domain-id').loadAdditionalFindingsByDomainId + const func = loadAdditionalFindingsByDomainId({ query, userKey: 'userKey', i18n }) + + await expect(func({ domainId: 'domainId' })).rejects.toThrow( + 'Unable to load additional findings. Please try again.', + ) + }) + + it('throws an error when a cursor error occurs', async () => { + const cursor = { + next: jest.fn(() => { + throw new Error() + }), + } + const query = jest.fn(() => cursor) + const loadAdditionalFindingsByDomainId = + require('../load-additional-findings-by-domain-id').loadAdditionalFindingsByDomainId + const func = loadAdditionalFindingsByDomainId({ query, userKey: 'userKey', i18n }) + + await expect(func({ domainId: 'domainId' })).rejects.toThrow( + 'Unable to load additional findings. Please try again.', + ) + }) + + it('returns the finding when everything is correct', async () => { + const finding = { id: 'findingId' } + const cursor = { next: jest.fn(() => finding) } + const query = jest.fn(() => cursor) + const loadAdditionalFindingsByDomainId = + require('../load-additional-findings-by-domain-id').loadAdditionalFindingsByDomainId + const func = loadAdditionalFindingsByDomainId({ query, userKey: 'userKey', i18n }) + + const result = await func({ domainId: 'domainId' }) + + expect(result).toEqual(finding) + }) +}) diff --git a/api/src/additional-findings/loaders/index.js b/api/src/additional-findings/loaders/index.js new file mode 100644 index 0000000000..004813d693 --- /dev/null +++ b/api/src/additional-findings/loaders/index.js @@ -0,0 +1,2 @@ +export * from './load-additional-findings-by-domain-id' +export * from './load-top-25-reports' diff --git a/api/src/additional-findings/loaders/load-additional-findings-by-domain-id.js b/api/src/additional-findings/loaders/load-additional-findings-by-domain-id.js new file mode 100644 index 0000000000..9e99303ee0 --- /dev/null +++ b/api/src/additional-findings/loaders/load-additional-findings-by-domain-id.js @@ -0,0 +1,38 @@ +import { t } from '@lingui/macro' + +export const loadAdditionalFindingsByDomainId = + ({ query, userKey, i18n }) => + async ({ domainId }) => { + if (domainId === undefined) { + console.warn(`User: ${userKey} did not set \`domainId\` argument for: loadAdditionalFindingsByDomainId.`) + throw new Error(i18n._(t`You must provide a \`domainId\` to retrieve a domain's additional findings.`)) + } + + let cursor + try { + cursor = await query` + WITH additionalFindings, domains + FOR finding IN additionalFindings + FILTER finding.domain == ${domainId} + LIMIT 1 + RETURN finding + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather additional findings for domain: ${domainId}. Error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load additional findings. Please try again.`)) + } + + let finding + try { + finding = await cursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather additional findings for domain: ${domainId}. Error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load additional findings. Please try again.`)) + } + + return finding + } diff --git a/api/src/additional-findings/loaders/load-top-25-reports.js b/api/src/additional-findings/loaders/load-top-25-reports.js new file mode 100644 index 0000000000..c511a5a9a8 --- /dev/null +++ b/api/src/additional-findings/loaders/load-top-25-reports.js @@ -0,0 +1,33 @@ +import { t } from '@lingui/macro' + +export const loadTop25Reports = + ({ query, userKey, i18n, language }) => + async () => { + let top25Report + + try { + top25Report = ( + await query` + LET verifiedOrgs = ( + FOR org IN organizations + FILTER org.verified == true + LET orgDetails = TRANSLATE(${language}, org.orgDetails) + RETURN { id: org._id, orgName: orgDetails.name, orgAcronym: orgDetails.acronym } + ) + FOR org IN verifiedOrgs + LET vulnDomainCount = COUNT( + FOR v, e IN 1..1 OUTBOUND org.id claims + OPTIONS { "bfs": true } + FILTER v.cveDetected == true + RETURN v.domain + ) + RETURN MERGE({ assetCount: vulnDomainCount }, org) + ` + ).all() + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadTop25Reports: ${err}`) + throw new Error(i18n._(t`Unable to load organization domain statuses. Please try again.`)) + } + + return top25Report + } diff --git a/api/src/additional-findings/objects/__tests__/additonal-finding.test.js b/api/src/additional-findings/objects/__tests__/additonal-finding.test.js new file mode 100644 index 0000000000..3d3e0906e7 --- /dev/null +++ b/api/src/additional-findings/objects/__tests__/additonal-finding.test.js @@ -0,0 +1,114 @@ +import { + additionalFinding, + webConnectionType, + webComponentLocationType, + webComponentPortType, + webComponentCveType, +} from '../additional-finding.js' +import { GraphQLList, GraphQLString } from 'graphql' +import { GraphQLDateTime } from 'graphql-scalars' + +describe('additionalFinding', () => { + it('should have correct fields', () => { + const fields = additionalFinding.getFields() + + expect(fields.timestamp.type).toBe(GraphQLDateTime) + expect(fields.timestamp.description).toBe('The date the finding was discovered.') + + expect(fields.locations.type).toBeInstanceOf(GraphQLList) + expect(fields.locations.description).toBe('The locations the finding was discovered.') + + expect(fields.ports.type).toBeInstanceOf(GraphQLList) + expect(fields.ports.description).toBe('The ports the finding was discovered.') + + expect(fields.headers.type).toBeInstanceOf(GraphQLList) + expect(fields.headers.description).toBe('The headers the finding was discovered.') + + expect(fields.webComponents.type).toBeInstanceOf(GraphQLList) + expect(fields.webComponents.description).toBe('The web components the finding was discovered.') + + expect(fields.vulnerabilities.type).toBeInstanceOf(GraphQLList) + expect(fields.vulnerabilities.description).toBe('The vulnerabilities the finding was discovered.') + }) +}) + +describe('webConnectionType', () => { + it('should have correct fields', () => { + const fields = webConnectionType.getFields() + + expect(fields.webComponentName.type).toBe(GraphQLString) + expect(fields.webComponentName.description).toBe('The URL of the web component.') + + expect(fields.webComponentCategory.type).toBe(GraphQLString) + expect(fields.webComponentCategory.description).toBe('The type of web component.') + + expect(fields.webComponentVersion.type).toBe(GraphQLString) + expect(fields.webComponentVersion.description).toBe('The status of the web component.') + + expect(fields.webComponentCves.type).toBeInstanceOf(GraphQLList) + expect(fields.webComponentPorts.type).toBeInstanceOf(GraphQLList) + + expect(fields.webComponentFirstSeen.type).toBe(GraphQLString) + expect(fields.webComponentLastSeen.type).toBe(GraphQLString) + }) +}) + +describe('webComponentLocationType', () => { + it('should have correct fields', () => { + const fields = webComponentLocationType.getFields() + + expect(fields.region.type).toBe(GraphQLString) + expect(fields.region.description).toBe('The location of the finding.') + + expect(fields.city.type).toBe(GraphQLString) + expect(fields.city.description).toBe('The location of the finding.') + + expect(fields.latitude.type).toBe(GraphQLString) + expect(fields.latitude.description).toBe('The location of the finding.') + + expect(fields.longitude.type).toBe(GraphQLString) + expect(fields.longitude.description).toBe('The location of the finding.') + + expect(fields.firstSeen.type).toBe(GraphQLString) + expect(fields.firstSeen.description).toBe('The location of the finding.') + + expect(fields.lastSeen.type).toBe(GraphQLString) + expect(fields.lastSeen.description).toBe('The location of the finding.') + }) +}) + +describe('webComponentPortType', () => { + it('should have correct fields', () => { + const fields = webComponentPortType.getFields() + + expect(fields.port.type).toBe(GraphQLString) + expect(fields.port.description).toBe('The port the finding was discovered.') + + expect(fields.lastPortState.type).toBe(GraphQLString) + expect(fields.lastPortState.description).toBe('The protocol the finding was discovered.') + + expect(fields.portStateFirstSeen.type).toBe(GraphQLString) + expect(fields.portStateFirstSeen.description).toBe('The date the finding was discovered.') + + expect(fields.portStateLastSeen.type).toBe(GraphQLString) + expect(fields.portStateLastSeen.description).toBe('The date the finding was discovered.') + }) +}) + +describe('webComponentCveType', () => { + it('should have correct fields', () => { + const fields = webComponentCveType.getFields() + + expect(fields.cve.type).toBe(GraphQLString) + expect(fields.cve.description).toBe('The CVE of the finding.') + + expect(fields.cwe.type).toBe(GraphQLString) + expect(fields.cwe.description).toBe('The description of the CVE.') + + expect(fields.cvssScore.type).toBe(GraphQLString) + expect(fields.cvssScore.description).toBe('The severity of the CVE.') + + expect(fields.cvss3Score.type).toBe(GraphQLString) + expect(fields.cvss3Score.description).toBe('The severity of the CVE.') + }) +}) diff --git a/api/src/additional-findings/objects/__tests__/cvd-enrollment.test.js b/api/src/additional-findings/objects/__tests__/cvd-enrollment.test.js new file mode 100644 index 0000000000..ebfced89b9 --- /dev/null +++ b/api/src/additional-findings/objects/__tests__/cvd-enrollment.test.js @@ -0,0 +1,61 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { EnrollmentStatusEnums, SeverityEnum, CvdRequirementEnums } from '../../../enums' +import { cvdEnrollment } from '../cvd-enrollment' + +describe('cvdEnrollment GraphQLObjectType', () => { + it('should be an instance of GraphQLObjectType', () => { + expect(cvdEnrollment).toBeInstanceOf(GraphQLObjectType) + }) + + it('should have the correct name and description', () => { + expect(cvdEnrollment.name).toBe('CvdEnrollment') + expect(cvdEnrollment.description).toBe( + 'Represents the CVD enrollment details for a domain asset, including enrollment status and CVSS environmental requirements.', + ) + }) + + describe('fields', () => { + const fields = cvdEnrollment.getFields() + + it('should include all expected fields', () => { + expect(fields).toHaveProperty('status') + expect(fields).toHaveProperty('description') + expect(fields).toHaveProperty('maxSeverity') + expect(fields).toHaveProperty('confidentialityRequirement') + expect(fields).toHaveProperty('integrityRequirement') + expect(fields).toHaveProperty('availabilityRequirement') + }) + + it('should have correct type and description for status', () => { + expect(fields.status.type).toBe(EnrollmentStatusEnums) + expect(fields.status.description).toBe( + 'The enrollment status of the asset in the Coordinated Vulnerability Disclosure (CVD) program.', + ) + }) + + it('should have correct type and description for description', () => { + expect(fields.description.type).toBe(GraphQLString) + expect(fields.description.description).toBe('The asset description.') + }) + + it('should have correct type and description for maxSeverity', () => { + expect(fields.maxSeverity.type).toBe(SeverityEnum) + expect(fields.maxSeverity.description).toContain('qualitative rating') + }) + + it('should have correct type and description for confidentialityRequirement', () => { + expect(fields.confidentialityRequirement.type).toBe(CvdRequirementEnums) + expect(fields.confidentialityRequirement.description).toContain('Confidentiality Impact') + }) + + it('should have correct type and description for integrityRequirement', () => { + expect(fields.integrityRequirement.type).toBe(CvdRequirementEnums) + expect(fields.integrityRequirement.description).toContain('Integrity Impact') + }) + + it('should have correct type and description for availabilityRequirement', () => { + expect(fields.availabilityRequirement.type).toBe(CvdRequirementEnums) + expect(fields.availabilityRequirement.description).toContain('Availability Impact') + }) + }) +}) diff --git a/api/src/additional-findings/objects/additional-finding.js b/api/src/additional-findings/objects/additional-finding.js new file mode 100644 index 0000000000..e842a01836 --- /dev/null +++ b/api/src/additional-findings/objects/additional-finding.js @@ -0,0 +1,196 @@ +import { GraphQLObjectType, GraphQLList, GraphQLString } from 'graphql' +import { GraphQLDateTime } from 'graphql-scalars' + +export const additionalFinding = new GraphQLObjectType({ + name: 'AdditionalFinding', + fields: () => ({ + timestamp: { + type: GraphQLDateTime, + description: `The date the finding was discovered.`, + resolve: ({ timestamp }) => new Date(timestamp), + }, + locations: { + type: new GraphQLList(webComponentLocationType), + description: `The locations the finding was discovered.`, + resolve: ({ locations }) => locations, + }, + ports: { + type: new GraphQLList(webComponentPortType), + description: `The ports the finding was discovered.`, + resolve: ({ ports }) => ports, + }, + headers: { + type: new GraphQLList(GraphQLString), + description: `The headers the finding was discovered.`, + resolve: ({ headers }) => headers, + }, + webComponents: { + type: new GraphQLList(webConnectionType), + description: `The web components the finding was discovered.`, + resolve: ({ webComponents }) => webComponents, + }, + vulnerabilities: { + type: new GraphQLList(webComponentCveType), + description: `The vulnerabilities the finding was discovered.`, + resolve: ({ webComponents }) => { + const vulnerabilities = [] + for (const webComponent of webComponents) { + vulnerabilities.push(...webComponent.WebComponentCves) + } + + const jsonObject = vulnerabilities.map(JSON.stringify) + const uniqueSet = new Set(jsonObject) + const uniqueVulns = Array.from(uniqueSet).map(JSON.parse) + uniqueVulns.sort((a, b) => Number(a.Cvss3Score) - Number(b.Cvss3Score)) + + return uniqueVulns + }, + }, + }), + description: `A finding imported from an external ASM tool.`, +}) + +export const webConnectionType = new GraphQLObjectType({ + name: 'WebConnectionType', + fields: () => ({ + webComponentName: { + type: GraphQLString, + description: `The URL of the web component.`, + resolve: ({ WebComponentName }) => WebComponentName, + }, + webComponentCategory: { + type: GraphQLString, + description: `The type of web component.`, + resolve: ({ WebComponentCategory }) => WebComponentCategory, + }, + webComponentVersion: { + type: GraphQLString, + description: `The status of the web component.`, + resolve: ({ WebComponentVersion }) => WebComponentVersion, + }, + webComponentCves: { + type: new GraphQLList(webComponentCveType), + description: '', + resolve: ({ WebComponentCves }) => WebComponentCves, + }, + webComponentPorts: { + type: new GraphQLList(webComponentPortType), + description: '', + resolve: ({ WebComponentPorts }) => WebComponentPorts, + }, + webComponentFirstSeen: { + type: GraphQLString, + description: '', + resolve: ({ WebComponentFirstSeen }) => WebComponentFirstSeen, + }, + webComponentLastSeen: { + type: GraphQLString, + description: '', + resolve: ({ WebComponentLastSeen }) => WebComponentLastSeen, + }, + }), +}) + +export const webComponentLocationType = new GraphQLObjectType({ + name: 'WebComponentLocation', + fields: () => ({ + region: { + type: GraphQLString, + description: `The location of the finding.`, + resolve: ({ Region }) => Region, + }, + city: { + type: GraphQLString, + description: `The location of the finding.`, + resolve: ({ City }) => City, + }, + latitude: { + type: GraphQLString, + description: `The location of the finding.`, + resolve: ({ Latitude }) => Latitude, + }, + longitude: { + type: GraphQLString, + description: `The location of the finding.`, + resolve: ({ Longitude }) => Longitude, + }, + firstSeen: { + type: GraphQLString, + description: `The location of the finding.`, + resolve: ({ FirstSeen }) => FirstSeen, + }, + lastSeen: { + type: GraphQLString, + description: `The location of the finding.`, + resolve: ({ LastSeen }) => LastSeen, + }, + }), +}) + +export const webComponentPortType = new GraphQLObjectType({ + name: 'WebComponentPort', + fields: () => ({ + port: { + type: GraphQLString, + description: `The port the finding was discovered.`, + resolve: ({ Port }) => Port, + }, + lastPortState: { + type: GraphQLString, + description: `The protocol the finding was discovered.`, + resolve: ({ LastPortState }) => LastPortState, + }, + portStateFirstSeen: { + type: GraphQLString, + description: `The date the finding was discovered.`, + resolve: ({ PortStateFirstSeen }) => PortStateFirstSeen, + }, + portStateLastSeen: { + type: GraphQLString, + description: `The date the finding was discovered.`, + resolve: ({ PortStateLastSeen }) => PortStateLastSeen, + }, + }), +}) + +export const webComponentCveType = new GraphQLObjectType({ + name: 'WebComponentCVE', + fields: () => ({ + cve: { + type: GraphQLString, + description: `The CVE of the finding.`, + resolve: ({ Cve }) => Cve, + }, + cwe: { + type: GraphQLString, + description: `The description of the CVE.`, + resolve: ({ Cwe }) => Cwe, + }, + cvssScore: { + type: GraphQLString, + description: `The severity of the CVE.`, + resolve: ({ CvssScore }) => CvssScore, + }, + cvss3Score: { + type: GraphQLString, + description: `The severity of the CVE.`, + resolve: ({ Cvss3Score }) => Number(Cvss3Score).toFixed(1), + }, + severity: { + type: GraphQLString, + description: `The severity of the CVE.`, + resolve: ({ Cvss3Score }) => { + const score = Number(Cvss3Score) + if (score >= 9) return 'critical' + else if (score >= 7 && score < 9) return 'high' + else if (score >= 4 && score < 7) return 'medium' + else return 'low' + }, + }, + confidenceLevel: { + type: GraphQLString, + description: 'Level of confidence that finding is accurate.', + resolve: ({ ConfidenceLevel }) => ConfidenceLevel, + }, + }), +}) diff --git a/api/src/additional-findings/objects/cvd-enrollment.js b/api/src/additional-findings/objects/cvd-enrollment.js new file mode 100644 index 0000000000..4cbb0d4ca8 --- /dev/null +++ b/api/src/additional-findings/objects/cvd-enrollment.js @@ -0,0 +1,38 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { EnrollmentStatusEnums, SeverityEnum, CvdRequirementEnums } from '../../enums' + +export const cvdEnrollmentFields = { + status: { + description: 'The enrollment status of the asset in the Coordinated Vulnerability Disclosure (CVD) program.', + type: EnrollmentStatusEnums, + }, + description: { + description: 'The asset description.', + type: GraphQLString, + }, + maxSeverity: { + description: + 'The qualitative rating of the maximum severity allowed on this asset. Its value is calculated from the combination of all three of the environmental requirements (CR, IR, and AR).', + type: SeverityEnum, + }, + confidentialityRequirement: { + description: + 'A CVSS environmental modifier that reweights Confidentiality Impact of a vulnerability on this asset.', + type: CvdRequirementEnums, + }, + integrityRequirement: { + description: 'A CVSS environmental modifier that reweights Integrity Impact of a vulnerability on this asset.', + type: CvdRequirementEnums, + }, + availabilityRequirement: { + description: 'A CVSS environmental modifier that reweights Availability Impact of a vulnerability on this asset.', + type: CvdRequirementEnums, + }, +} + +export const cvdEnrollment = new GraphQLObjectType({ + name: 'CvdEnrollment', + description: + 'Represents the CVD enrollment details for a domain asset, including enrollment status and CVSS environmental requirements.', + fields: () => ({ ...cvdEnrollmentFields }), +}) diff --git a/api/src/additional-findings/objects/index.js b/api/src/additional-findings/objects/index.js new file mode 100644 index 0000000000..18e5dd687a --- /dev/null +++ b/api/src/additional-findings/objects/index.js @@ -0,0 +1,2 @@ +export * from './additional-finding' +export * from './cvd-enrollment' diff --git a/api/src/additional-findings/queries/__tests__/get-top-25-reports.test.js b/api/src/additional-findings/queries/__tests__/get-top-25-reports.test.js new file mode 100644 index 0000000000..3ecc2ee53d --- /dev/null +++ b/api/src/additional-findings/queries/__tests__/get-top-25-reports.test.js @@ -0,0 +1,435 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { checkSuperAdmin, superAdminRequired, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { loadTop25Reports } from '../../loaders' +import dbschema from '../../../../database.json' +import { setupI18n } from '@lingui/core' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given getTop25Reports', () => { + // eslint-disable-next-line no-unused-vars + let query, drop, truncate, schema, collections, superAdminOrg, domainOne, domainTwo, orgOne, orgTwo, i18n, user + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + consoleOutput.length = 0 + }) + beforeEach(async () => { + user = await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + superAdminOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'NFED', + sector: 'NTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'NPFED', + sector: 'NPTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + + orgOne = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'org-one', + acronym: 'OO', + name: 'Org One', + zone: 'NFED', + sector: 'NTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'org-one', + acronym: 'OO', + name: 'Org One', + zone: 'NPFED', + sector: 'NPTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + + orgTwo = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'org-two', + acronym: 'OT', + name: 'Org Two', + zone: 'NFED', + sector: 'NTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'org-two', + acronym: 'OT', + name: 'Org Two', + zone: 'NPFED', + sector: 'NPTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + + domainOne = await collections.domains.save({ + domain: 'domain.one', + status: { + https: 'fail', + hsts: 'pass', + certificates: 'pass', + ciphers: 'pass', + curves: 'pass', + protocols: 'pass', + spf: 'pass', + dkim: 'pass', + dmarc: 'pass', + }, + rcode: 'NOERROR', + blocked: false, + wildcardSibling: false, + hasEntrustCertificate: false, + cveDetected: true, + }) + domainTwo = await collections.domains.save({ + domain: 'domain.two', + status: { + https: 'pass', + hsts: 'fail', + certificates: 'pass', + ciphers: 'fail', + curves: 'pass', + protocols: 'fail', + spf: 'pass', + dkim: 'pass', + dmarc: 'fail', + }, + rcode: 'NOERROR', + blocked: false, + wildcardSibling: false, + hasEntrustCertificate: false, + cveDetected: true, + }) + + await collections.claims.save({ + _from: orgOne._id, + _to: domainOne._id, + }) + await collections.claims.save({ + _from: orgTwo._id, + _to: domainTwo._id, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + let loginRequiredBool + describe('login is not required', () => { + beforeEach(async () => { + loginRequiredBool = false + }) + describe('the user is not a super admin', () => { + it('returns a permission error', async () => { + const response = await graphql({ + schema, + source: ` + query { + getTop25Reports + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + language: 'en', + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { + additionalFindings: { + getTop25Reports: loadTop25Reports({ query, userKey: user._key, i18n, language: 'en' }), + }, + }, + }, + }) + const error = [ + new GraphQLError('Permissions error. You do not have sufficient permissions to access this data.'), + ] + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to access controlled functionality without sufficient privileges.`, + ]) + }) + }) + describe('the user is a super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + }) + + it('returns all domain status results', async () => { + const response = await graphql({ + schema, + source: ` + query { + getTop25Reports + } + `, + rootValue: null, + contextValue: { + i18n, + language: 'en', + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { + additionalFindings: { + getTop25Reports: loadTop25Reports({ query, userKey: user._key, i18n, language: 'en' }), + }, + }, + }, + }) + + const expectedResponse = { + data: { + getTop25Reports: `orgName,orgAcronym,assetCount +"Org One","OO","1" +"Org Two","OT","1" +Government of Canada,GC,2`, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved all top 25 reports.`]) + }) + }) + }) + describe('login is required', () => { + beforeEach(async () => { + loginRequiredBool = true + }) + describe('the user is not a super admin', () => { + it('returns a permission error', async () => { + const response = await graphql({ + schema, + source: ` + query { + getTop25Reports + } + `, + rootValue: null, + contextValue: { + i18n, + language: 'en', + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { + additionalFindings: { + getTop25Reports: loadTop25Reports({ query, userKey: user._key, i18n, language: 'en' }), + }, + }, + }, + }) + const error = [ + new GraphQLError('Permissions error. You do not have sufficient permissions to access this data.'), + ] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to access controlled functionality without sufficient privileges.`, + ]) + }) + }) + describe('the user is a super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + }) + + it('returns all domain status results', async () => { + const response = await graphql({ + schema, + source: ` + query { + getTop25Reports + } + `, + rootValue: null, + contextValue: { + i18n, + language: 'en', + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { + additionalFindings: { + getTop25Reports: loadTop25Reports({ query, userKey: user._key, i18n, language: 'en' }), + }, + }, + }, + }) + const expectedResponse = { + data: { + getTop25Reports: `orgName,orgAcronym,assetCount +"Org One","OO","1" +"Org Two","OT","1" +Government of Canada,GC,2`, + }, + } + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved all top 25 reports.`]) + }) + }) + }) +}) diff --git a/api/src/additional-findings/queries/get-top-25-report.js b/api/src/additional-findings/queries/get-top-25-report.js new file mode 100644 index 0000000000..ec770e1419 --- /dev/null +++ b/api/src/additional-findings/queries/get-top-25-report.js @@ -0,0 +1,45 @@ +import { GraphQLString } from 'graphql' + +export const getTop25Reports = { + type: GraphQLString, + description: 'CSV formatted output of top 25 reports.', + resolve: async ( + _, + args, + { + userKey, + auth: { checkSuperAdmin, userRequired, verifiedRequired, superAdminRequired }, + dataSources: { additionalFindings }, + language, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + const top25Reports = await additionalFindings.getTop25Reports({ ...args }) + + console.info(`User ${userKey} successfully retrieved all top 25 reports.`) + + if (top25Reports === undefined) return top25Reports + + const headers = ['orgName', 'orgAcronym', 'assetCount'] + let csvOutput = headers.join(',') + let totalAssetCount = 0 + top25Reports.forEach((domainStatus) => { + const csvLine = headers + .map((header) => { + return `"${domainStatus[header]}"` + }) + .join(',') + csvOutput += `\n${csvLine}` + totalAssetCount += domainStatus.assetCount + }) + const govName = language === 'en' ? 'Government of Canada' : 'Gouvernement du Canada' + csvOutput += `\n${govName},GC,${totalAssetCount}` + + return csvOutput + }, +} diff --git a/api/src/additional-findings/queries/index.js b/api/src/additional-findings/queries/index.js new file mode 100644 index 0000000000..8542ff42c1 --- /dev/null +++ b/api/src/additional-findings/queries/index.js @@ -0,0 +1 @@ +export * from './get-top-25-report' diff --git a/api-js/src/affiliation/index.js b/api/src/affiliation/index.js similarity index 100% rename from api-js/src/affiliation/index.js rename to api/src/affiliation/index.js diff --git a/api/src/affiliation/inputs/__tests__/affiliation-org-order.test.js b/api/src/affiliation/inputs/__tests__/affiliation-org-order.test.js new file mode 100644 index 0000000000..09ebd154d3 --- /dev/null +++ b/api/src/affiliation/inputs/__tests__/affiliation-org-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { affiliationOrgOrder } from '../affiliation-org-order' +import { OrderDirection, AffiliationOrgOrderField } from '../../../enums' + +describe('given the affiliationOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = affiliationOrgOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = affiliationOrgOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(AffiliationOrgOrderField)) + }) + }) +}) diff --git a/api/src/affiliation/inputs/__tests__/affiliation-user-order.test.js b/api/src/affiliation/inputs/__tests__/affiliation-user-order.test.js new file mode 100644 index 0000000000..c01050edc4 --- /dev/null +++ b/api/src/affiliation/inputs/__tests__/affiliation-user-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { affiliationUserOrder } from '../affiliation-user-order' +import { OrderDirection, AffiliationUserOrderField } from '../../../enums' + +describe('given the affiliationOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = affiliationUserOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = affiliationUserOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(AffiliationUserOrderField)) + }) + }) +}) diff --git a/api-js/src/affiliation/inputs/affiliation-org-order.js b/api/src/affiliation/inputs/affiliation-org-order.js similarity index 81% rename from api-js/src/affiliation/inputs/affiliation-org-order.js rename to api/src/affiliation/inputs/affiliation-org-order.js index b2ded8a1ff..f820407cb7 100644 --- a/api-js/src/affiliation/inputs/affiliation-org-order.js +++ b/api/src/affiliation/inputs/affiliation-org-order.js @@ -7,11 +7,11 @@ export const affiliationOrgOrder = new GraphQLInputObjectType({ description: 'Ordering options for affiliation connections.', fields: () => ({ field: { - type: GraphQLNonNull(AffiliationOrgOrderField), + type: new GraphQLNonNull(AffiliationOrgOrderField), description: 'The field to order affiliations by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/affiliation/inputs/affiliation-user-order.js b/api/src/affiliation/inputs/affiliation-user-order.js similarity index 81% rename from api-js/src/affiliation/inputs/affiliation-user-order.js rename to api/src/affiliation/inputs/affiliation-user-order.js index 780921c7f2..31525d117b 100644 --- a/api-js/src/affiliation/inputs/affiliation-user-order.js +++ b/api/src/affiliation/inputs/affiliation-user-order.js @@ -7,11 +7,11 @@ export const affiliationUserOrder = new GraphQLInputObjectType({ description: 'Ordering options for affiliation connections.', fields: () => ({ field: { - type: GraphQLNonNull(AffiliationUserOrderField), + type: new GraphQLNonNull(AffiliationUserOrderField), description: 'The field to order affiliations by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/affiliation/inputs/index.js b/api/src/affiliation/inputs/index.js similarity index 100% rename from api-js/src/affiliation/inputs/index.js rename to api/src/affiliation/inputs/index.js diff --git a/api-js/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js b/api/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js similarity index 87% rename from api-js/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js rename to api/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js index 3b0ac1bfc8..d6649a47e0 100644 --- a/api-js/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js +++ b/api/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { databaseOptions } from '../../../../database-options' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { loadAffiliationByKey } from '..' import { setupI18n } from '@lingui/core' - import englishMessages from '../../../locale/en/messages' + import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -25,11 +26,15 @@ describe('given a loadAffiliationByKey dataloader', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) @@ -37,7 +42,6 @@ describe('given a loadAffiliationByKey dataloader', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -168,9 +172,7 @@ describe('given a loadAffiliationByKey dataloader', () => { }) describe('database error is raised', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadAffiliationByKey({ query: mockedQuery, userKey: '1234', @@ -180,9 +182,7 @@ describe('given a loadAffiliationByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to find user affiliation(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to find user affiliation(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -207,9 +207,7 @@ describe('given a loadAffiliationByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to find user affiliation(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to find user affiliation(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -235,9 +233,7 @@ describe('given a loadAffiliationByKey dataloader', () => { }) describe('database error is raised', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadAffiliationByKey({ query: mockedQuery, userKey: '1234', @@ -248,9 +244,7 @@ describe('given a loadAffiliationByKey dataloader', () => { await loader.load('1') } catch (err) { expect(err).toEqual( - new Error( - `Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.`, - ), + new Error(`Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.`), ) } @@ -277,9 +271,7 @@ describe('given a loadAffiliationByKey dataloader', () => { await loader.load('1') } catch (err) { expect(err).toEqual( - new Error( - `Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.`, - ), + new Error(`Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.`), ) } diff --git a/api-js/src/affiliation/loaders/__tests__/load-affiliation-connections-by-org-id.test.js b/api/src/affiliation/loaders/__tests__/load-affiliation-connections-by-org-id.test.js similarity index 84% rename from api-js/src/affiliation/loaders/__tests__/load-affiliation-connections-by-org-id.test.js rename to api/src/affiliation/loaders/__tests__/load-affiliation-connections-by-org-id.test.js index e88d001968..fcbe68e91e 100644 --- a/api-js/src/affiliation/loaders/__tests__/load-affiliation-connections-by-org-id.test.js +++ b/api/src/affiliation/loaders/__tests__/load-affiliation-connections-by-org-id.test.js @@ -1,16 +1,14 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' -import { - loadAffiliationConnectionsByOrgId, - loadAffiliationByKey, -} from '../index' +import { loadAffiliationConnectionsByOrgId, loadAffiliationByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -31,25 +29,27 @@ describe('given the load affiliations by org id function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) userTwo = await collections.users.save({ userName: 'test.accounttwo@istio.actually.exists', displayName: 'Jane Doe', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -119,10 +119,7 @@ describe('given the load affiliations by org id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -149,14 +146,8 @@ describe('given the load affiliations by org id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), }, } @@ -173,10 +164,7 @@ describe('given the load affiliations by org id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -193,10 +181,7 @@ describe('given the load affiliations by org id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -206,14 +191,8 @@ describe('given the load affiliations by org id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -230,10 +209,7 @@ describe('given the load affiliations by org id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -249,10 +225,7 @@ describe('given the load affiliations by org id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -262,14 +235,8 @@ describe('given the load affiliations by org id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -286,10 +253,7 @@ describe('given the load affiliations by org id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -305,10 +269,7 @@ describe('given the load affiliations by org id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[1]._key), node: { ...expectedAffiliations[1], }, @@ -318,14 +279,8 @@ describe('given the load affiliations by org id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), }, } @@ -352,10 +307,7 @@ describe('given the load affiliations by org id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -372,27 +324,18 @@ describe('given the load affiliations by org id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, }, ], - totalCount: 2, + totalCount: 1, pageInfo: { - hasNextPage: true, + hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -409,10 +352,7 @@ describe('given the load affiliations by org id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -429,27 +369,18 @@ describe('given the load affiliations by org id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, }, ], - totalCount: 2, + totalCount: 1, pageInfo: { - hasNextPage: true, + hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -526,7 +457,7 @@ describe('given the load affiliations by org id function', () => { permission: 'user', }) }) - describe('ordering by USER_USERNAME', () => { + describe('ordering by USERNAME', () => { describe('direction is set to ASC', () => { it('returns affiliation', async () => { const expectedAffiliation = await loadAffiliationByKey({ @@ -548,7 +479,7 @@ describe('given the load affiliations by org id function', () => { after: toGlobalId('affiliation', affOne._key), before: toGlobalId('affiliation', affThree._key), orderBy: { - field: 'user-username', + field: 'username', direction: 'ASC', }, } @@ -597,7 +528,7 @@ describe('given the load affiliations by org id function', () => { after: toGlobalId('affiliation', affThree._key), before: toGlobalId('affiliation', affOne._key), orderBy: { - field: 'user-username', + field: 'username', direction: 'DESC', }, } @@ -692,9 +623,7 @@ describe('given the load affiliations by org id function', () => { await affiliationLoader({ orgId: org._id, ...connectionArgs }) } catch (err) { expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.', - ), + new Error('Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.'), ) } @@ -751,11 +680,7 @@ describe('given the load affiliations by org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `Affiliation` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `Affiliation` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadAffiliationConnectionsByOrgId.`, @@ -781,11 +706,7 @@ describe('given the load affiliations by org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `Affiliation` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `Affiliation` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadAffiliationConnectionsByOrgId.`, @@ -858,9 +779,7 @@ describe('given the load affiliations by org id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const affiliationLoader = loadAffiliationConnectionsByOrgId({ query, userKey: user._key, @@ -877,11 +796,7 @@ describe('given the load affiliations by org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -893,9 +808,7 @@ describe('given the load affiliations by org id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const affiliationLoader = loadAffiliationConnectionsByOrgId({ query, userKey: user._key, @@ -912,11 +825,7 @@ describe('given the load affiliations by org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -931,11 +840,7 @@ describe('given the load affiliations by org id function', () => { describe('given a database error', () => { describe('while querying affiliations', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query organizations. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query organizations. Please try again.')) const affiliationLoader = loadAffiliationConnectionsByOrgId({ query, @@ -950,9 +855,7 @@ describe('given the load affiliations by org id function', () => { try { await affiliationLoader({ orgId: org._id, ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error('Unable to query affiliation(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to query affiliation(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -984,9 +887,7 @@ describe('given the load affiliations by org id function', () => { try { await affiliationLoader({ orgId: org._id, ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load affiliation(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load affiliation(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1088,9 +989,7 @@ describe('given the load affiliations by org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` sur la connexion \`Affiliation\` ne peut être inférieur à zéro.`, - ), + new Error(`\`first\` sur la connexion \`Affiliation\` ne peut être inférieur à zéro.`), ) } expect(consoleOutput).toEqual([ @@ -1117,11 +1016,7 @@ describe('given the load affiliations by org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` sur la connexion \`Affiliation\` ne peut être inférieur à zéro.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` sur la connexion \`Affiliation\` ne peut être inférieur à zéro.`)) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadAffiliationConnectionsByOrgId.`, @@ -1194,9 +1089,7 @@ describe('given the load affiliations by org id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const affiliationLoader = loadAffiliationConnectionsByOrgId({ query, userKey: user._key, @@ -1214,9 +1107,7 @@ describe('given the load affiliations by org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -1229,9 +1120,7 @@ describe('given the load affiliations by org id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const affiliationLoader = loadAffiliationConnectionsByOrgId({ query, userKey: user._key, @@ -1249,9 +1138,7 @@ describe('given the load affiliations by org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -1267,11 +1154,7 @@ describe('given the load affiliations by org id function', () => { describe('given a database error', () => { describe('while querying affiliations', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query organizations. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query organizations. Please try again.')) const affiliationLoader = loadAffiliationConnectionsByOrgId({ query, @@ -1286,11 +1169,7 @@ describe('given the load affiliations by org id function', () => { try { await affiliationLoader({ orgId: org._id, ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - `Impossible de demander l'affiliation (s). Veuillez réessayer.`, - ), - ) + expect(err).toEqual(new Error(`Impossible de demander l'affiliation (s). Veuillez réessayer.`)) } expect(consoleOutput).toEqual([ @@ -1322,11 +1201,7 @@ describe('given the load affiliations by org id function', () => { try { await affiliationLoader({ orgId: org._id, ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - `Impossible de charger l'affiliation (s). Veuillez réessayer.`, - ), - ) + expect(err).toEqual(new Error(`Impossible de charger l'affiliation (s). Veuillez réessayer.`)) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/affiliation/loaders/__tests__/load-affiliation-connections-by-user-id.test.js b/api/src/affiliation/loaders/__tests__/load-affiliation-connections-by-user-id.test.js similarity index 94% rename from api-js/src/affiliation/loaders/__tests__/load-affiliation-connections-by-user-id.test.js rename to api/src/affiliation/loaders/__tests__/load-affiliation-connections-by-user-id.test.js index dfa1aa5c01..3de9c42f6d 100644 --- a/api-js/src/affiliation/loaders/__tests__/load-affiliation-connections-by-user-id.test.js +++ b/api/src/affiliation/loaders/__tests__/load-affiliation-connections-by-user-id.test.js @@ -1,13 +1,14 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadAffiliationConnectionsByUserId, loadAffiliationByKey } from '..' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -31,18 +32,21 @@ describe('given the load affiliations by user id function', () => { let affOne, affTwo beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -123,10 +127,7 @@ describe('given the load affiliations by user id function', () => { }) const affLoader = loadAffiliationByKey({ query }) - const expectedAffiliations = await affLoader.loadMany([ - affOne._key, - affTwo._key, - ]) + const expectedAffiliations = await affLoader.loadMany([affOne._key, affTwo._key]) expectedAffiliations[0].id = expectedAffiliations[0]._key expectedAffiliations[1].id = expectedAffiliations[1]._key @@ -143,10 +144,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[1]._key), node: { ...expectedAffiliations[1], }, @@ -156,14 +154,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), }, } @@ -199,10 +191,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -212,14 +201,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -254,10 +237,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -267,14 +247,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -309,10 +283,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[1]._key), node: { ...expectedAffiliations[1], }, @@ -322,14 +293,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[1]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[1]._key), }, } @@ -376,10 +341,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -389,14 +351,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -432,10 +388,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -445,14 +398,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -490,10 +437,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -503,14 +447,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -546,10 +484,7 @@ describe('given the load affiliations by user id function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + cursor: toGlobalId('affiliation', expectedAffiliations[0]._key), node: { ...expectedAffiliations[0], }, @@ -559,14 +494,8 @@ describe('given the load affiliations by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), - endCursor: toGlobalId( - 'affiliation', - expectedAffiliations[0]._key, - ), + startCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), + endCursor: toGlobalId('affiliation', expectedAffiliations[0]._key), }, } @@ -576,16 +505,7 @@ describe('given the load affiliations by user id function', () => { }) }) describe('using orderBy field', () => { - let affOne, - affTwo, - affThree, - domainOne, - domainTwo, - domainThree, - orgOne, - orgTwo, - orgThree, - userOne + let affOne, affTwo, affThree, domainOne, domainTwo, domainThree, orgOne, orgTwo, orgThree, userOne beforeEach(async () => { await truncate() userOne = await collections.users.save({ @@ -1298,9 +1218,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1350,9 +1268,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1400,9 +1316,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1452,9 +1366,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1502,9 +1414,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1554,9 +1464,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1604,9 +1512,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1656,9 +1562,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1706,9 +1610,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1758,9 +1660,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1808,9 +1708,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1860,9 +1758,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1910,9 +1806,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -1962,9 +1856,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2012,9 +1904,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2064,9 +1954,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2114,9 +2002,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2166,9 +2052,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2216,9 +2100,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2268,9 +2150,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2318,9 +2198,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2372,9 +2250,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2422,9 +2298,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2474,9 +2348,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2524,9 +2396,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2576,9 +2446,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2626,9 +2494,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2678,9 +2544,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2728,9 +2592,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2780,9 +2642,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2830,9 +2690,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2882,9 +2740,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2932,9 +2788,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -2984,9 +2838,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -3034,9 +2886,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -3086,9 +2936,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -3136,9 +2984,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -3188,9 +3034,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -3238,9 +3082,7 @@ describe('given the load affiliations by user id function', () => { it('returns affiliation', async () => { const affiliationLoader = loadAffiliationByKey({ query }) - const expectedAffiliation = await affiliationLoader.load( - affTwo._key, - ) + const expectedAffiliation = await affiliationLoader.load(affTwo._key) const connectionLoader = loadAffiliationConnectionsByUserId({ query, @@ -4046,9 +3888,7 @@ describe('given the load affiliations by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.', - ), + new Error('Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.'), ) } @@ -4107,11 +3947,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `Affiliation` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `Affiliation` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadAffiliationConnectionsByUserId.`, @@ -4138,11 +3974,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `Affiliation` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `Affiliation` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadAffiliationConnectionsByUserId.`, @@ -4217,9 +4049,7 @@ describe('given the load affiliations by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadAffiliationConnectionsByUserId({ query, language: 'en', @@ -4237,11 +4067,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -4253,9 +4079,7 @@ describe('given the load affiliations by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadAffiliationConnectionsByUserId({ query, language: 'en', @@ -4273,11 +4097,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -4292,11 +4112,7 @@ describe('given the load affiliations by user id function', () => { describe('given a database error', () => { describe('while querying domains', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query organizations. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query organizations. Please try again.')) const affiliationLoader = loadAffiliationConnectionsByUserId({ query, @@ -4315,9 +4131,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to query affiliation(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to query affiliation(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -4353,9 +4167,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load affiliation(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load affiliation(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -4463,11 +4275,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `Affiliation` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`first` sur la connexion `Affiliation` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadAffiliationConnectionsByUserId.`, @@ -4494,11 +4302,7 @@ describe('given the load affiliations by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `Affiliation` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `Affiliation` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadAffiliationConnectionsByUserId.`, @@ -4573,9 +4377,7 @@ describe('given the load affiliations by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadAffiliationConnectionsByUserId({ query, language: 'en', @@ -4594,9 +4396,7 @@ describe('given the load affiliations by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -4609,9 +4409,7 @@ describe('given the load affiliations by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadAffiliationConnectionsByUserId({ query, language: 'en', @@ -4630,9 +4428,7 @@ describe('given the load affiliations by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -4648,11 +4444,7 @@ describe('given the load affiliations by user id function', () => { describe('given a database error', () => { describe('while querying domains', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query organizations. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query organizations. Please try again.')) const affiliationLoader = loadAffiliationConnectionsByUserId({ query, @@ -4668,11 +4460,7 @@ describe('given the load affiliations by user id function', () => { try { await affiliationLoader({ userId: user._id, ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de demander l'affiliation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de demander l'affiliation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -4705,11 +4493,7 @@ describe('given the load affiliations by user id function', () => { try { await affiliationLoader({ userId: user._id, ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'affiliation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'affiliation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/affiliation/loaders/index.js b/api/src/affiliation/loaders/index.js similarity index 100% rename from api-js/src/affiliation/loaders/index.js rename to api/src/affiliation/loaders/index.js diff --git a/api-js/src/affiliation/loaders/load-affiliation-by-key.js b/api/src/affiliation/loaders/load-affiliation-by-key.js similarity index 92% rename from api-js/src/affiliation/loaders/load-affiliation-by-key.js rename to api/src/affiliation/loaders/load-affiliation-by-key.js index 1681a28305..de45608d6a 100644 --- a/api-js/src/affiliation/loaders/load-affiliation-by-key.js +++ b/api/src/affiliation/loaders/load-affiliation-by-key.js @@ -1,7 +1,7 @@ import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadAffiliationByKey = ({ query, userKey, i18n }) => +export const loadAffiliationByKey = ({query, userKey, i18n}) => new DataLoader(async (ids) => { let cursor diff --git a/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js b/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js new file mode 100644 index 0000000000..578943de5a --- /dev/null +++ b/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js @@ -0,0 +1,336 @@ +import { aql } from 'arangojs' +import { fromGlobalId, toGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +export const loadAffiliationConnectionsByOrgId = + ({ query, userKey, cleanseInput, i18n }) => + async ({ orgId, after, before, first, last, orderBy, search, includePending }) => { + let afterTemplate = aql`` + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + let affiliationField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'username') { + affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` + documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._to).key).userName` + } else if (orderBy.field === 'display_name') { + affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).displayName` + documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._to).key).displayName` + } else if (orderBy.field === 'permission') { + affiliationField = aql`rolePriority[affiliation.permission]` + documentField = aql`rolePriority[DOCUMENT(affiliations, ${afterId}).permission]` + } + + afterTemplate = aql` + FILTER ${affiliationField} ${afterTemplateDirection} ${documentField} + OR (${affiliationField} == ${documentField} + AND TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + let affiliationField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'username') { + affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` + documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._to).key).userName` + } else if (orderBy.field === 'display_name') { + affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).displayName` + documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._to).key).displayName` + } else if (orderBy.field === 'permission') { + affiliationField = aql`rolePriority[affiliation.permission]` + documentField = aql`rolePriority[DOCUMENT(affiliations, ${beforeId}).permission]` + } + + beforeTemplate = aql` + FILTER ${affiliationField} ${beforeTemplateDirection} ${documentField} + OR (${affiliationField} == ${documentField} + AND TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Affiliation\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Affiliation\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` on the \`Affiliation\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Affiliation\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(affiliation._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(affiliation._key) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let affField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'username') { + affField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` + hasNextPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._to).key).userName` + hasPreviousPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._to).key).userName` + } else if (orderBy.field === 'display_name') { + affField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).displayName` + hasNextPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._to).key).displayName` + hasPreviousPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._to).key).displayName` + } else if (orderBy.field === 'permission') { + affField = aql`rolePriority[affiliation.permission]` + hasNextPageDocument = aql`rolePriority[LAST(retrievedAffiliations).permission]` + hasPreviousPageDocument = aql`FIRST(retrievedAffiliations).permission` + } + + hasNextPageFilter = aql` + FILTER ${affField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${affField} == ${hasNextPageDocument} + AND TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)) + ` + + hasPreviousPageFilter = aql` + FILTER ${affField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${affField} == ${hasPreviousPageDocument} + AND TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'username') { + sortByField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName ${orderBy.direction},` + } else if (orderBy.field === 'display_name') { + sortByField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).displayName ${orderBy.direction},` + } else if (orderBy.field === 'permission') { + sortByField = aql`rolePriority[affiliation.permission] ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let userSearchQuery = aql`` + let userIdFilter = aql`` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + userSearchQuery = aql` + LET tokenArr = TOKENS(${search}, "text_en") + LET searchedDisplayNamesCount = FLATTEN( + FOR tokenItem in tokenArr + LET token = LOWER(tokenItem) + FOR user IN userSearch + SEARCH ANALYZER( + user.displayName LIKE CONCAT("%", token, "%") + , "text_en") + COLLECT currentUserId = user._id WITH COUNT INTO count + RETURN { + 'user': currentUserId, + 'count': count + } + ) + LET searchedDisplayNames = searchedDisplayNamesCount[* FILTER CURRENT.count == LENGTH(tokenArr)].user + LET searchedUserNames = ( + FOR user IN users + FILTER LOWER(user.userName) LIKE CONCAT("%", LOWER(${search}), "%") + RETURN user._id + ) + LET userIds = UNIQUE(APPEND(searchedDisplayNames, searchedUserNames)) + + ` + userIdFilter = aql`FILTER e._to IN userIds` + } + + let pendingFilter = aql`FILTER e.permission != "pending"` + if (includePending) { + pendingFilter = aql`` + } + + let filteredAffiliationCursor + try { + filteredAffiliationCursor = await query` + WITH affiliations, organizations, users, userSearch + + ${userSearchQuery} + + LET affiliationKeys = ( + FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations + ${userIdFilter} + ${pendingFilter} + RETURN e._key + ) + + LET rolePriority = { + "pending": 0, + "owner": 1, + "super_admin": 2, + "admin": 3, + "user": 4 + } + + LET retrievedAffiliations = ( + FOR affiliation IN affiliations + FILTER affiliation._key IN affiliationKeys + LET orgKey = PARSE_IDENTIFIER(affiliation._from).key + LET userKey = PARSE_IDENTIFIER(affiliation._to).key + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + id: affiliation._key, + orgKey: orgKey, + userKey: userKey, + _type: "affiliation" + }, + affiliation + ) + ) + + LET hasNextPage = (LENGTH( + FOR affiliation IN affiliations + FILTER affiliation._key IN affiliationKeys + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 + RETURN affiliation + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR affiliation IN affiliations + FILTER affiliation._key IN affiliationKeys + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 + RETURN affiliation + ) > 0 ? true : false) + + RETURN { + "affiliations": retrievedAffiliations, + "totalCount": LENGTH(affiliationKeys), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedAffiliations)._key, + "endKey": LAST(retrievedAffiliations)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to query affiliation(s). Please try again.`)) + } + + let filteredAffiliations + try { + filteredAffiliations = await filteredAffiliationCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load affiliation(s). Please try again.`)) + } + + if (filteredAffiliations.affiliations.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = filteredAffiliations.affiliations.map((affiliation) => { + return { + cursor: toGlobalId('affiliation', affiliation._key), + node: affiliation, + } + }) + + return { + edges, + totalCount: filteredAffiliations.totalCount, + pageInfo: { + hasNextPage: filteredAffiliations.hasNextPage, + hasPreviousPage: filteredAffiliations.hasPreviousPage, + startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), + endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), + }, + } + } diff --git a/api/src/affiliation/loaders/load-affiliation-connections-by-user-id.js b/api/src/affiliation/loaders/load-affiliation-connections-by-user-id.js new file mode 100644 index 0000000000..7e14151d75 --- /dev/null +++ b/api/src/affiliation/loaders/load-affiliation-connections-by-user-id.js @@ -0,0 +1,483 @@ +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' +import {t} from '@lingui/macro' + +export const loadAffiliationConnectionsByUserId = + ({query, language, userKey, cleanseInput, i18n}) => + async ({userId, after, before, first, last, orderBy, search}) => { + let afterTemplate = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + let affiliationField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'org-acronym') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).acronym` + } else if (orderBy.field === 'org-name') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).name` + } else if (orderBy.field === 'org-slug') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).slug` + } else if (orderBy.field === 'org-zone') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).zone` + } else if (orderBy.field === 'org-sector') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).sector` + } else if (orderBy.field === 'org-country') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).country` + } else if (orderBy.field === 'org-province') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).province` + } else if (orderBy.field === 'org-city') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).orgDetails).city` + } else if (orderBy.field === 'org-verified') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).verified` + } else if (orderBy.field === 'org-summary-mail-pass') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.mail.pass` + } else if (orderBy.field === 'org-summary-mail-fail') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.mail.fail` + } else if (orderBy.field === 'org-summary-mail-total') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.mail.total` + } else if (orderBy.field === 'org-summary-web-pass') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.web.pass` + } else if (orderBy.field === 'org-summary-web-fail') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.web.fail` + } else if (orderBy.field === 'org-summary-web-total') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).summaries.web.total` + } else if (orderBy.field === 'org-domain-count') { + affiliationField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to)` + documentField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key)._id claims RETURN e._to)` + } + + afterTemplate = aql` + FILTER ${affiliationField} ${afterTemplateDirection} ${documentField} + OR (${affiliationField} == ${documentField} + AND TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + let affiliationField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'org-acronym') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).acronym` + } else if (orderBy.field === 'org-name') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).name` + } else if (orderBy.field === 'org-slug') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).slug` + } else if (orderBy.field === 'org-zone') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).zone` + } else if (orderBy.field === 'org-sector') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).sector` + } else if (orderBy.field === 'org-country') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).country` + } else if (orderBy.field === 'org-province') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).province` + } else if (orderBy.field === 'org-city') { + affiliationField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city` + documentField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).orgDetails).city` + } else if (orderBy.field === 'org-verified') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).verified` + } else if (orderBy.field === 'org-summary-mail-pass') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.mail.pass` + } else if (orderBy.field === 'org-summary-mail-fail') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.mail.fail` + } else if (orderBy.field === 'org-summary-mail-total') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.mail.total` + } else if (orderBy.field === 'org-summary-web-pass') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.web.pass` + } else if (orderBy.field === 'org-summary-web-fail') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.web.fail` + } else if (orderBy.field === 'org-summary-web-total') { + affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total` + documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).summaries.web.total` + } else if (orderBy.field === 'org-domain-count') { + affiliationField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to)` + documentField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key)._id claims RETURN e._to)` + } + + beforeTemplate = aql` + FILTER ${affiliationField} ${beforeTemplateDirection} ${documentField} + OR (${affiliationField} == ${documentField} + AND TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAffiliationConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`Affiliation\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAffiliationConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`Affiliation\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAffiliationConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`Affiliation\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAffiliationConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Affiliation\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(affiliation._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(affiliation._key) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAffiliationConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let affField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'org-acronym') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).acronym` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).acronym` + } else if (orderBy.field === 'org-name') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).name` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).name` + } else if (orderBy.field === 'org-slug') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).slug` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).slug` + } else if (orderBy.field === 'org-zone') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).zone` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).zone` + } else if (orderBy.field === 'org-sector') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).sector` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).sector` + } else if (orderBy.field === 'org-country') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).country` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).country` + } else if (orderBy.field === 'org-province') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).province` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).province` + } else if (orderBy.field === 'org-city') { + affField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city` + hasNextPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).orgDetails).city` + hasPreviousPageDocument = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).orgDetails).city` + } else if (orderBy.field === 'org-verified') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).verified` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).verified` + } else if (orderBy.field === 'org-summary-mail-pass') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.mail.pass` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.mail.pass` + } else if (orderBy.field === 'org-summary-mail-fail') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.mail.fail` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.mail.fail` + } else if (orderBy.field === 'org-summary-mail-total') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.mail.total` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.mail.total` + } else if (orderBy.field === 'org-summary-web-pass') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.web.pass` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.web.pass` + } else if (orderBy.field === 'org-summary-web-fail') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.web.fail` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.web.fail` + } else if (orderBy.field === 'org-summary-web-total') { + affField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total` + hasNextPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).summaries.web.total` + hasPreviousPageDocument = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).summaries.web.total` + } else if (orderBy.field === 'org-domain-count') { + affField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to)` + hasNextPageDocument = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key)._id claims RETURN e._to)` + hasPreviousPageDocument = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key)._id claims RETURN e._to)` + } + + hasNextPageFilter = aql` + FILTER ${affField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${affField} == ${hasNextPageDocument} + AND TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)) + ` + + hasPreviousPageFilter = aql` + FILTER ${affField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${affField} == ${hasPreviousPageDocument} + AND TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'org-acronym') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym ${orderBy.direction},` + } else if (orderBy.field === 'org-name') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name ${orderBy.direction},` + } else if (orderBy.field === 'org-slug') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug ${orderBy.direction},` + } else if (orderBy.field === 'org-zone') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone ${orderBy.direction},` + } else if (orderBy.field === 'org-sector') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector ${orderBy.direction},` + } else if (orderBy.field === 'org-country') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country ${orderBy.direction},` + } else if (orderBy.field === 'org-province') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province ${orderBy.direction},` + } else if (orderBy.field === 'org-city') { + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city ${orderBy.direction},` + } else if (orderBy.field === 'org-verified') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified ${orderBy.direction},` + } else if (orderBy.field === 'org-summary-mail-pass') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.pass ${orderBy.direction},` + } else if (orderBy.field === 'org-summary-mail-fail') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.fail ${orderBy.direction},` + } else if (orderBy.field === 'org-summary-mail-total') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.mail.total ${orderBy.direction},` + } else if (orderBy.field === 'org-summary-web-pass') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.pass ${orderBy.direction},` + } else if (orderBy.field === 'org-summary-web-fail') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.fail ${orderBy.direction},` + } else if (orderBy.field === 'org-summary-web-total') { + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).summaries.web.total ${orderBy.direction},` + } else if (orderBy.field === 'org-domain-count') { + sortByField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to) ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let orgSearchQuery = aql`` + let orgIdFilter = aql`` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + orgSearchQuery = aql` + LET tokenArrEN = TOKENS(${search}, "text_en") + LET searchOrgsEN = UNIQUE( + FOR token IN tokenArrEN + FOR org IN organizationSearch + SEARCH ANALYZER( + org.orgDetails.en.acronym LIKE CONCAT("%", token, "%") + OR org.orgDetails.en.name LIKE CONCAT("%", token, "%") + , "text_en") + RETURN org._id + ) + LET tokenArrFR = TOKENS(${search}, "text_fr") + LET searchedOrgsFR = UNIQUE( + FOR token IN tokenArrFR + FOR org IN organizationSearch + SEARCH ANALYZER( + org.orgDetails.fr.acronym LIKE CONCAT("%", token, "%") + OR org.orgDetails.fr.name LIKE CONCAT("%", token, "%") + , "text_fr") + RETURN org._id + ) + LET orgIds = UNION_DISTINCT(searchOrgsEN, searchedOrgsFR) + ` + orgIdFilter = aql`FILTER e._from IN orgIds` + } + + let filteredAffiliationCursor + try { + filteredAffiliationCursor = await query` + WITH affiliations, organizations, organizationSearch users + + ${orgSearchQuery} + + LET affiliationKeys = ( + FOR v, e IN 1..1 INBOUND ${userId} affiliations + ${orgIdFilter} + RETURN e._key + ) + + LET retrievedAffiliations = ( + FOR affiliation IN affiliations + FILTER affiliation._key IN affiliationKeys + LET orgKey = PARSE_IDENTIFIER(affiliation._from).key + LET userKey = PARSE_IDENTIFIER(affiliation._to).key + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE(affiliation, { id: affiliation._key, orgKey: orgKey, userKey: userKey, _type: "affiliation" }) + ) + + LET hasNextPage = (LENGTH( + FOR affiliation IN affiliations + FILTER affiliation._key IN affiliationKeys + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 + RETURN affiliation + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR affiliation IN affiliations + FILTER affiliation._key IN affiliationKeys + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(affiliation._key) ${sortString} LIMIT 1 + RETURN affiliation + ) > 0 ? true : false) + + RETURN { + "affiliations": retrievedAffiliations, + "totalCount": LENGTH(affiliationKeys), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedAffiliations)._key, + "endKey": LAST(retrievedAffiliations)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query affiliations in loadAffiliationConnectionsByUserId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to query affiliation(s). Please try again.`), + ) + } + + let filteredAffiliations + try { + filteredAffiliations = await filteredAffiliationCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather affiliations in loadAffiliationConnectionsByUserId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load affiliation(s). Please try again.`), + ) + } + + if (filteredAffiliations.affiliations.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = filteredAffiliations.affiliations.map((affiliation) => { + return { + cursor: toGlobalId('affiliation', affiliation._key), + node: affiliation, + } + }) + + return { + edges, + totalCount: filteredAffiliations.totalCount, + pageInfo: { + hasNextPage: filteredAffiliations.hasNextPage, + hasPreviousPage: filteredAffiliations.hasPreviousPage, + startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), + endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), + }, + } + } diff --git a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js new file mode 100644 index 0000000000..cac682c504 --- /dev/null +++ b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js @@ -0,0 +1,1749 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' +import { cleanseInput } from '../../../validators' +import { loadOrgByKey, loadOrganizationNamesById } from '../../../organization/loaders' +import { loadUserByKey, loadUserByUserName } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env + +describe('invite user to org', () => { + let query, drop, truncate, schema, collections, transaction, i18n, tokenize, user, org, userToInvite + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + tokenize = jest.fn().mockReturnValue('token') + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful invitation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + beforeAll(() => {}) + let org + beforeEach(async () => { + org = await ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + describe('users role is super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + describe('inviting an existing account', () => { + describe('requested role is super_admin', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: SUPER_ADMIN + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully invited user to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + }) + }) + }) + describe('requested role is admin', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: ADMIN + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully invited user to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + }) + }) + }) + describe('requested role is user', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully invited user to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + }) + }) + }) + }) + describe('inviting a non-existing account', () => { + describe('requested role is super_admin', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: SUPER_ADMIN + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully sent invitation to service, and organization email.', + }, + }, + }, + } + + const token = tokenize({ + parameters: { + userName: 'test@email.gc.ca', + orgId: org._id, + requestedRole: 'super_admin', + }, + }) + const createAccountLink = `https://host/create-user/${token}` + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ + user: { + userName: 'test@email.gc.ca', + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + createAccountLink, + }) + }) + }) + describe('requested role is admin', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: ADMIN + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully sent invitation to service, and organization email.', + }, + }, + }, + } + + const token = tokenize({ + parameters: { + userName: 'test@email.gc.ca', + orgId: org._id, + requestedRole: 'admin', + }, + }) + const createAccountLink = `https://host/create-user/${token}` + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ + user: { + userName: 'test@email.gc.ca', + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + createAccountLink, + }) + }) + }) + describe('requested role is user', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully sent invitation to service, and organization email.', + }, + }, + }, + } + + const token = tokenize({ + parameters: { + userName: 'test@email.gc.ca', + orgId: org._id, + requestedRole: 'user', + }, + }) + const createAccountLink = `https://host/create-user/${token}` + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ + user: { + userName: 'test@email.gc.ca', + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + createAccountLink, + }) + }) + }) + }) + }) + describe('users role is admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + }) + describe('inviting an existing account', () => { + describe('requested role is admin', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: ADMIN + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully invited user to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + }) + }) + }) + describe('requested role is user', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully invited user to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: ${secondaryUser._key} to the org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + }) + }) + }) + }) + describe('inviting a non-existing account', () => { + describe('requested role is admin', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: ADMIN + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully sent invitation to service, and organization email.', + }, + }, + }, + } + + const token = tokenize({ + parameters: { + userName: 'test@email.gc.ca', + orgId: org._id, + requestedRole: 'admin', + }, + }) + const createAccountLink = `https://host/create-user/${token}` + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ + user: { + userName: 'test@email.gc.ca', + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + createAccountLink, + }) + }) + }) + describe('requested role is user', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully sent invitation to service, and organization email.', + }, + }, + }, + } + + const token = tokenize({ + parameters: { + userName: 'test@email.gc.ca', + orgId: org._id, + requestedRole: 'user', + }, + }) + const createAccountLink = `https://host/create-user/${token}` + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: treasury-board-secretariat.`, + ]) + expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ + user: { + userName: 'test@email.gc.ca', + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + createAccountLink, + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful invitation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + user = ( + await collections.users.save( + { + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }, + { returnNew: true }, + ) + ).new + userToInvite = ( + await collections.users.save( + { + userName: 'usertoinvite@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }, + { returnNew: true }, + ) + ).new + org = ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('user attempts to invite themselves', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test.account@istio.actually.exists" + requestedRole: USER + orgId: "${toGlobalId('organizations', 1)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn(), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 400, + description: 'Unable to invite yourself to an org.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([`User: 123 attempted to invite themselves to 1.`]) + }) + }) + describe('user attempts to invite to an org that does not exist', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', 1)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 400, + description: 'Unable to invite user to unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to invite user: test@email.gc.ca to 1 however there is no org associated with that id.`, + ]) + }) + }) + describe('user with undefined permission attempts to invite a user', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue(undefined), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 403, + description: 'Permission Denied: Please contact organization admin for help with user invitations.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: user but does not have permission to do so.`, + ]) + }) + }) + describe('user with user level permission attempts to invite a user', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: USER + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('user'), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 403, + description: 'Permission Denied: Please contact organization admin for help with user invitations.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: user but does not have permission to do so.`, + ]) + }) + }) + describe('user with admin level permission attempts to invite a user to super_admin permission', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "test@email.gc.ca" + requestedRole: SUPER_ADMIN + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 403, + description: 'Permission Denied: Please contact super admin for help with user invitations.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to invite user: test@email.gc.ca to org: 123 with role: super_admin but does not have permission to do so.`, + ]) + }) + }) + describe('transaction error occurs', () => { + describe('when creating affiliation', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "${userToInvite.userName}" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx step err'), + abort: jest.fn(), + }), + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 500, + description: 'Unable to invite user. Please try again.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: treasury-board-secretariat, error: trx step err`, + ]) + }) + }) + describe('when committing transaction', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + inviteUserToOrg( + input: { + userName: "${userToInvite.userName}" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn(), + commit: jest.fn().mockRejectedValue('trx commit err'), + abort: jest.fn(), + }), + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { + sendOrgInviteCreateAccount: jest.fn(), + sendOrgInviteEmail: jest.fn(), + }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + inviteUserToOrg: { + result: { + code: 500, + description: 'Unable to invite user. Please try again.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction commit error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: treasury-board-secretariat, error: trx commit err`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/affiliation/mutations/__tests__/leave-organization.test.js b/api/src/affiliation/mutations/__tests__/leave-organization.test.js new file mode 100644 index 0000000000..61400c9785 --- /dev/null +++ b/api/src/affiliation/mutations/__tests__/leave-organization.test.js @@ -0,0 +1,980 @@ +import { setupI18n } from '@lingui/core' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { checkOrgOwner, userRequired, verifiedRequired } from '../../../auth' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import { cleanseInput } from '../../../validators' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env + +describe('given a successful leave', () => { + let query, drop, truncate, schema, collections, transaction, i18n, user, org, domain, domain2 + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + domain2 = await collections.domains.save({ + domain: 'test.canada.ca', + slug: 'test-canada-ca', + }) + await collections.claims.save({ + _from: org._id, + _to: domain2._id, + }) + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, + }) + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, + }) + + const webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) + + const dmarcSummary = await collections.dmarcSummaries.save({ + dmarcSummary: true, + }) + await collections.domainsToDmarcSummaries.save({ + _from: domain._id, + _to: dmarcSummary._id, + }) + }) + afterEach(async () => { + await truncate() + await drop() + consoleOutput.length = 0 + }) + + describe('user is an org owner', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'owner', + }) + }) + describe('only one org claims the domains', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns status success message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkOrgOwner: checkOrgOwner({ + i18n, + query, + userKey: user._key, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: 'Successfully left organization: treasury-board-secretariat', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns status success message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkOrgOwner: checkOrgOwner({ + i18n, + query, + userKey: user._key, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: "L'organisation a été quittée avec succès: treasury-board-secretariat", + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) + }) + }) + }) + describe('multiple orgs claims the domains', () => { + let org2 + beforeEach(async () => { + org2 = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat-2', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor-2', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.claims.save({ + _from: org2._id, + _to: domain._id, + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns status success message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkOrgOwner: checkOrgOwner({ + i18n, + query, + userKey: user._key, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: 'Successfully left organization: treasury-board-secretariat', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns status success message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkOrgOwner: checkOrgOwner({ + i18n, + query, + userKey: user._key, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: "L'organisation a été quittée avec succès: treasury-board-secretariat", + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) + }) + }) + }) + }) +}) +describe('given an unsuccessful leave', () => { + let schema, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(async () => { + consoleOutput.length = 0 + }) + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('org cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn(), + collections: jest.fn(), + transaction: jest.fn(), + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + validators: { cleanseInput }, + }, + }) + + const expectedResult = { + data: { + leaveOrganization: { + result: { + code: 400, + description: 'Unable to leave undefined organization.', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User 123 attempted to leave undefined organization: 123`]) + }) + }) + describe('user is not an org owner', () => { + describe('when removing affiliation information', () => { + it('throws an error', async () => { + const mockedQuery = jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([]), + }) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Step error occurred.')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: jest.fn({ property: 'string' }), + transaction: mockedTransaction, + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Unable leave organization. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when removing user: 123 affiliation with org: 123: Error: Step error occurred.`, + ]) + }) + }) + }) + describe('transaction commit error occurs', () => { + it('throws an error', async () => { + const mockedQuery = jest.fn().mockReturnValue({ + count: 1, + next: jest.fn().mockReturnValue({ + count: 1, + }), + }) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(new Error('Step error occurred.')), + commit: jest.fn().mockRejectedValue(new Error('Trx Commit Error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: jest.fn({ property: 'string' }), + transaction: mockedTransaction, + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Unable leave organization. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to leave org: 123: Error: Trx Commit Error`, + ]) + }) + }) + }) + describe('language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('org cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn(), + collections: jest.fn(), + transaction: jest.fn(), + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + validators: { cleanseInput }, + }, + }) + + const expectedResult = { + data: { + leaveOrganization: { + result: { + code: 400, + description: 'Impossible de quitter une organisation non définie.', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User 123 attempted to leave undefined organization: 123`]) + }) + }) + describe('user is not an org owner', () => { + describe('when removing affiliation information', () => { + it('throws an error', async () => { + const mockedQuery = jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([]), + }) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Step error occurred.')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: jest.fn({ property: 'string' }), + transaction: mockedTransaction, + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError("Impossible de quitter l'organisation. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when removing user: 123 affiliation with org: 123: Error: Step error occurred.`, + ]) + }) + }) + }) + describe('transaction commit error occurs', () => { + it('throws an error', async () => { + const mockedQuery = jest.fn().mockReturnValue({ + count: 1, + next: jest.fn().mockReturnValue({ + count: 1, + }), + }) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(new Error('Step error occurred.')), + commit: jest.fn().mockRejectedValue(new Error('Trx Commit Error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: jest.fn({ property: 'string' }), + transaction: mockedTransaction, + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _key: 123 }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError("Impossible de quitter l'organisation. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to leave org: 123: Error: Trx Commit Error`, + ]) + }) + }) + }) +}) diff --git a/api-js/src/affiliation/mutations/__tests__/remove-user-from-org.test.js b/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js similarity index 80% rename from api-js/src/affiliation/mutations/__tests__/remove-user-from-org.test.js rename to api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js index 94a6eedd27..a7140084c1 100644 --- a/api-js/src/affiliation/mutations/__tests__/remove-user-from-org.test.js +++ b/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js @@ -1,18 +1,20 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' import { loadOrgByKey } from '../../../organization/loaders' import { loadUserByKey } from '../../../user/loaders' import { loadAffiliationByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -95,32 +97,21 @@ const orgTwoData = { const adminData = { userName: 'admin.account@istio.actually.exists', displayName: 'Test Admin', - preferredLang: 'french', tfaValidated: false, emailValidated: true, + tfaSendMethod: 'email', } const userData = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: true, + tfaSendMethod: 'email', } describe('given the removeUserFromOrg mutation', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - orgOne, - orgTwo, - admin, - affiliation + let query, drop, truncate, schema, collections, transaction, i18n, user, orgOne, orgTwo, admin, affiliation const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -155,11 +146,15 @@ describe('given the removeUserFromOrg mutation', () => { describe('given a successful mutation', () => { beforeEach(async () => { ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) orgOne = await collections.organizations.save(orgOneData) orgTwo = await collections.organizations.save(orgTwoData) @@ -185,9 +180,9 @@ describe('given the removeUserFromOrg mutation', () => { }) describe('super admin can remove an admin from any org', () => { it('removes the admin from the org', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -211,13 +206,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -234,6 +230,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -250,7 +247,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const loader = loadAffiliationByKey({ query, @@ -260,10 +257,10 @@ describe('given the removeUserFromOrg mutation', () => { const data = await loader.load(affiliation._key) - expect(data).toEqual(undefined) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(data).toEqual(undefined) }) describe('users language is set to english', () => { beforeAll(() => { @@ -281,9 +278,9 @@ describe('given the removeUserFromOrg mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -307,13 +304,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -330,6 +328,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -346,7 +345,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { @@ -362,10 +361,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('users language is set to french', () => { @@ -384,9 +383,9 @@ describe('given the removeUserFromOrg mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -410,13 +409,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -433,6 +433,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -449,14 +450,13 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { removeUserFromOrg: { result: { - status: - "L'utilisateur a été retiré de l'organisation avec succès.", + status: "L'utilisateur a été retiré de l'organisation avec succès.", user: { id: toGlobalId('user', user._key), userName: 'test.account@istio.actually.exists', @@ -466,18 +466,18 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) describe('super admin can remove a user from any org', () => { it('removes the user from the org', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -501,13 +501,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -524,6 +525,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -540,7 +542,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const loader = loadAffiliationByKey({ query, @@ -550,10 +552,10 @@ describe('given the removeUserFromOrg mutation', () => { const data = await loader.load(affiliation._key) - expect(data).toEqual(undefined) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(data).toEqual(undefined) }) describe('users language is set to english', () => { beforeAll(() => { @@ -571,9 +573,9 @@ describe('given the removeUserFromOrg mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -597,13 +599,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -620,6 +623,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -636,7 +640,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { @@ -652,10 +656,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('users language is set to french', () => { @@ -674,9 +678,9 @@ describe('given the removeUserFromOrg mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -700,13 +704,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -723,6 +728,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -739,14 +745,13 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { removeUserFromOrg: { result: { - status: - "L'utilisateur a été retiré de l'organisation avec succès.", + status: "L'utilisateur a été retiré de l'organisation avec succès.", user: { id: toGlobalId('user', user._key), userName: 'test.account@istio.actually.exists', @@ -756,10 +761,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -783,9 +788,9 @@ describe('given the removeUserFromOrg mutation', () => { }) describe('admin can remove a user from the shared org', () => { it('removes the user from the org', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -809,13 +814,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -832,6 +838,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -848,7 +855,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const loader = loadAffiliationByKey({ query, @@ -858,10 +865,10 @@ describe('given the removeUserFromOrg mutation', () => { const data = await loader.load(affiliation._key) - expect(data).toEqual(undefined) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(data).toEqual(undefined) }) describe('users language is set to english', () => { beforeAll(() => { @@ -879,9 +886,9 @@ describe('given the removeUserFromOrg mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -905,13 +912,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -928,6 +936,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -944,7 +953,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { @@ -960,10 +969,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('users language is set to french', () => { @@ -982,9 +991,9 @@ describe('given the removeUserFromOrg mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1008,13 +1017,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -1031,6 +1041,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -1047,14 +1058,13 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { removeUserFromOrg: { result: { - status: - "L'utilisateur a été retiré de l'organisation avec succès.", + status: "L'utilisateur a été retiré de l'organisation avec succès.", user: { id: toGlobalId('user', user._key), userName: 'test.account@istio.actually.exists', @@ -1064,10 +1074,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -1091,9 +1101,9 @@ describe('given the removeUserFromOrg mutation', () => { describe('given an unsuccessful removal', () => { describe('org is not found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1117,19 +1127,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1141,24 +1153,23 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: - 'Unable to remove user from unknown organization.', + description: 'Unable to remove user from unknown organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no org with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is not an admin', () => { @@ -1171,9 +1182,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1197,19 +1208,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1223,23 +1236,23 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: 'Unable to remove user from organization.', + description: 'Permission Denied: Please contact organization admin for help with removing users.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is an admin for another org', () => { @@ -1252,9 +1265,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1278,19 +1291,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1304,23 +1319,23 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: 'Unable to remove user from organization.', + description: 'Permission Denied: Please contact organization admin for help with removing users.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requested user is not found', () => { @@ -1333,9 +1348,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1359,19 +1374,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1383,24 +1400,23 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: - 'Unable to remove unknown user from organization.', + description: 'Unable to remove unknown user from organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no user with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requested user does not belong to this org', () => { @@ -1410,9 +1426,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1436,19 +1452,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('super_admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1462,201 +1480,34 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: - 'Unable to remove a user that already does not belong to this organization.', + description: 'Unable to remove a user that already does not belong to this organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456, but they do not have any affiliations to org: 12345.`, ]) - }) - }) - describe('super admin attempts to remove another super admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'super_admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 12345 }), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with removing users.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) - }) - }) - describe('admin attempts to remove another admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 12345 }), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with removing users.', - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) }) }) }) describe('database error occurs', () => { describe('when checking requested users permission in requested org', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('database error')) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1680,19 +1531,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1706,18 +1559,14 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: database error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1730,9 +1579,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1756,19 +1605,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1782,18 +1633,14 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Cursor error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: cursor error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1808,11 +1655,12 @@ describe('given the removeUserFromOrg mutation', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1836,19 +1684,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1862,18 +1712,14 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx step error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx step error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1888,11 +1734,12 @@ describe('given the removeUserFromOrg mutation', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -1916,19 +1763,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -1942,18 +1791,14 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx commit error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx commit error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1975,9 +1820,9 @@ describe('given the removeUserFromOrg mutation', () => { describe('given an unsuccessful removal', () => { describe('org is not found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2001,19 +1846,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2025,24 +1872,23 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: - "Impossible de supprimer un utilisateur d'une organisation inconnue.", + description: "Impossible de supprimer un utilisateur d'une organisation inconnue.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no org with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is not an admin', () => { @@ -2055,9 +1901,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2081,19 +1927,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2107,7 +1955,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { @@ -2115,16 +1963,16 @@ describe('given the removeUserFromOrg mutation', () => { result: { code: 400, description: - "Impossible de supprimer un utilisateur de l'organisation.", + "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is an admin for another org', () => { @@ -2137,9 +1985,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2163,19 +2011,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2189,7 +2039,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { @@ -2197,16 +2047,16 @@ describe('given the removeUserFromOrg mutation', () => { result: { code: 400, description: - "Impossible de supprimer un utilisateur de l'organisation.", + "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requested user is not found', () => { @@ -2219,9 +2069,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2245,19 +2095,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2269,24 +2121,23 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { removeUserFromOrg: { result: { code: 400, - description: - "Impossible de supprimer un utilisateur inconnu de l'organisation.", + description: "Impossible de supprimer un utilisateur inconnu de l'organisation.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no user with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requested user does not belong to this org', () => { @@ -2296,9 +2147,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2322,19 +2173,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('super_admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2348,7 +2201,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { @@ -2362,187 +2215,21 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456, but they do not have any affiliations to org: 12345.`, ]) - }) - }) - describe('super admin attempts to remove another super admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'super_admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 12345 }), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) - }) - }) - describe('admin attempts to remove another admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 12345 }), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { cleanseInput }, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) }) }) }) describe('database error occurs', () => { describe('when checking requested users permission in requested org', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('database error')) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2566,19 +2253,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2592,18 +2281,16 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: database error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2616,9 +2303,9 @@ describe('given the removeUserFromOrg mutation', () => { } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2642,19 +2329,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2668,18 +2357,16 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Cursor error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: cursor error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2694,11 +2381,12 @@ describe('given the removeUserFromOrg mutation', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2722,19 +2410,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2748,18 +2438,16 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx step error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx step error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2774,11 +2462,12 @@ describe('given the removeUserFromOrg mutation', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -2802,19 +2491,21 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ _key: '123', }), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, loaders: { loadOrgByKey: { @@ -2828,18 +2519,16 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx commit error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx commit error`, ]) + expect(response.errors).toEqual(error) }) }) }) diff --git a/api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js b/api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js new file mode 100644 index 0000000000..fc9ce588cb --- /dev/null +++ b/api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js @@ -0,0 +1,632 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { userRequired, verifiedRequired } from '../../../auth' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' +import { cleanseInput } from '../../../validators' +import { loadOrgByKey, loadOrganizationNamesById } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env + +describe('invite user to org', () => { + let query, drop, truncate, schema, collections, transaction, i18n, tokenize, user, org + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + tokenize = jest.fn().mockReturnValue('token') + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + // given a successful request to join an org + describe('given a successful request to join an org', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + org = await ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + describe('users role is super admin', () => { + describe('inviting an existing account', () => { + describe('requested role is admin', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + await collections.affiliations.save({ + _from: org._id, + _to: secondaryUser._id, + permission: 'admin', + }) + }) + it('returns status message', async () => { + const sendInviteRequestEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendInviteRequestEmail: sendInviteRequestEmail }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + requestOrgAffiliation: { + result: { + status: 'Successfully requested invite to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully requested invite to the org: treasury-board-secretariat.`, + ]) + expect(sendInviteRequestEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', + adminLink: 'https://host/admin/organizations', + }) + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful invitation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + }) + beforeEach(async () => { + user = ( + await collections.users.save( + { + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }, + { returnNew: true }, + ) + ).new + org = ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user attempts to request an invite to an org that does not exist', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', 1)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + requestOrgAffiliation: { + result: { + code: 400, + description: 'Unable to request invite to unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to request invite to org: 1 however there is no org associated with that id.`, + ]) + }) + }) + describe('user has already requested to join org', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'pending', + }) + }) + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _id: org._id }), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + requestOrgAffiliation: { + result: { + code: 400, + description: + 'Unable to request invite to organization with which you have already requested to join.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to request invite to org: ${org._key} however they have already requested to join that org.`, + ]) + }) + }) + describe('user is already a member of org', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _id: org._id }), + }, + loadUserByKey: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + const error = { + data: { + requestOrgAffiliation: { + result: { + code: 400, + description: 'Unable to request invite to organization with which you are already affiliated.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to request invite to org: ${org._key} however they are already affiliated with that org.`, + ]) + }) + }) + }) + describe('transaction error occurs', () => { + describe('when creating affiliation', () => { + it('returns an error message', async () => { + await graphql({ + schema, + source: ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx step err'), + }), + userKey: 123, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + expect(consoleOutput).toEqual([ + `Transaction step error occurred while user: 123 attempted to request invite to org: treasury-board-secretariat, error: trx step err`, + ]) + }) + }) + describe('when committing transaction', () => { + it('returns an error message', async () => { + await graphql({ + schema, + source: ` mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx commit err'), + }), + userKey: 123, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + }) + + expect(consoleOutput).toEqual([ + `Transaction step error occurred while user: 123 attempted to request invite to org: treasury-board-secretariat, error: trx commit err`, + ]) + }) + }) + }) + }) +}) diff --git a/api-js/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js b/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js similarity index 81% rename from api-js/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js rename to api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js index df38c89e50..86728b11e0 100644 --- a/api-js/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js +++ b/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js @@ -1,31 +1,24 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { checkOrgOwner, userRequired, verifiedRequired } from '../../../auth' import { loadOrgByKey } from '../../../organization/loaders' import { loadUserByKey } from '../../../user/loaders' import { cleanseInput } from '../../../validators' import { createMutationSchema } from '../../../mutation' import { createQuerySchema } from '../../../query' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env describe('given the transferOrgOwnership mutation', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - user2, - org + let query, drop, truncate, schema, collections, transaction, i18n, user, user2, org const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -59,11 +52,15 @@ describe('given the transferOrgOwnership mutation', () => { describe('given a successful transfer', () => { beforeAll(async () => { ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -103,14 +100,12 @@ describe('given the transferOrgOwnership mutation', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) await collections.affiliations.save({ _from: org._id, _to: user2._id, permission: 'admin', - owner: false, }) }) afterEach(async () => { @@ -121,9 +116,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('user is the org owner', () => { it('sets owner field in the requesting users to false', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -143,11 +138,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -178,20 +173,20 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const testAffiliationCursor = await query` - FOR aff IN affiliations + FOR aff IN affiliations FILTER aff._to == ${user._id} RETURN aff ` const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toMatchObject({ owner: false }) + expect(testAffiliation).toMatchObject({ permission: 'admin' }) }) it('sets owner field in the requested users to true', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -211,11 +206,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -246,15 +241,15 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const testAffiliationCursor = await query` - FOR aff IN affiliations + FOR aff IN affiliations FILTER aff._to == ${user2._id} RETURN aff ` const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toMatchObject({ owner: true }) + expect(testAffiliation).toMatchObject({ permission: 'owner' }) }) describe('users language is set to english', () => { beforeAll(() => { @@ -270,9 +265,9 @@ describe('given the transferOrgOwnership mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -292,11 +287,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -331,7 +326,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -364,9 +359,9 @@ describe('given the transferOrgOwnership mutation', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -386,11 +381,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -425,7 +420,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -464,9 +459,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('requested org is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -486,11 +481,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -516,108 +511,28 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) - - const expectedResult = { - data: { - transferOrgOwnership: { - result: { - code: 400, - description: - 'Unable to transfer ownership of undefined organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer org ownership of an undefined org.`, - ]) - }) - }) - describe('requested org is verified', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - transferOrgOwnership ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - userId: "${toGlobalId('users', user2._key)}" - } - ) { - result { - ... on TransferOrgOwnershipResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: true, - slug: 'mocked-org', - }), - }, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) + }) const expectedResult = { data: { transferOrgOwnership: { result: { code: 400, - description: - 'Unable to transfer ownership of a verified organization.', + description: 'Unable to transfer ownership of undefined organization.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer ownership of a verified org: mocked-org.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to transfer org ownership of an undefined org.`]) }) }) describe('requesting user is not the org owner', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -637,11 +552,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -666,15 +581,14 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { transferOrgOwnership: { result: { code: 400, - description: - 'Permission Denied: Please contact org owner to transfer ownership.', + description: 'Permission Denied: Please contact org owner to transfer ownership.', }, }, }, @@ -688,9 +602,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('requested user is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -710,11 +624,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -737,15 +651,14 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { transferOrgOwnership: { result: { code: 400, - description: - 'Unable to transfer ownership of an org to an undefined user.', + description: 'Unable to transfer ownership of an org to an undefined user.', }, }, }, @@ -759,9 +672,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('requested user does not belong to the requested org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -781,11 +694,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 0 }), - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -810,7 +723,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -833,9 +746,9 @@ describe('given the transferOrgOwnership mutation', () => { describe('database error occurs', () => { describe('when checking requested users affiliations', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -855,11 +768,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('Database error')), - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -884,13 +797,9 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -904,11 +813,12 @@ describe('given the transferOrgOwnership mutation', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('Step Error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -928,11 +838,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: user._key, auth: { @@ -957,13 +867,9 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -974,15 +880,13 @@ describe('given the transferOrgOwnership mutation', () => { describe('when adding ownership to requested user', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Step Error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1002,11 +906,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: user._key, auth: { @@ -1031,13 +935,9 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1051,11 +951,12 @@ describe('given the transferOrgOwnership mutation', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), commit: jest.fn().mockRejectedValue(new Error('Commit Error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1075,11 +976,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: user._key, auth: { @@ -1104,13 +1005,9 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1136,9 +1033,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('requested org is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1158,11 +1055,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1188,108 +1085,28 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) - - const expectedResult = { - data: { - transferOrgOwnership: { - result: { - code: 400, - description: - "Impossible de transférer la propriété d'une organisation non définie.", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer org ownership of an undefined org.`, - ]) - }) - }) - describe('requested org is verified', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - transferOrgOwnership ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - userId: "${toGlobalId('users', user2._key)}" - } - ) { - result { - ... on TransferOrgOwnershipResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: true, - slug: 'mocked-org', - }), - }, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: { cleanseInput }, - }, - ) + }) const expectedResult = { data: { transferOrgOwnership: { result: { code: 400, - description: - "Impossible de transférer la propriété d'une organisation vérifiée.", + description: "Impossible de transférer la propriété d'une organisation non définie.", }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer ownership of a verified org: mocked-org.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to transfer org ownership of an undefined org.`]) }) }) describe('requesting user is not the org owner', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1309,11 +1126,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1338,7 +1155,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -1360,9 +1177,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('requested user is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1382,11 +1199,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1409,15 +1226,14 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { transferOrgOwnership: { result: { code: 400, - description: - "Impossible de transférer la propriété d'un org à un utilisateur non défini.", + description: "Impossible de transférer la propriété d'un org à un utilisateur non défini.", }, }, }, @@ -1431,9 +1247,9 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('requested user does not belong to the requested org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1453,11 +1269,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 0 }), - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1482,7 +1298,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -1505,9 +1321,9 @@ describe('given the transferOrgOwnership mutation', () => { describe('database error occurs', () => { describe('when checking requested users affiliations', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1527,11 +1343,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('Database error')), - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1556,12 +1372,10 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) @@ -1576,11 +1390,12 @@ describe('given the transferOrgOwnership mutation', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('Step Error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1600,11 +1415,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: user._key, auth: { @@ -1629,12 +1444,10 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) @@ -1646,15 +1459,13 @@ describe('given the transferOrgOwnership mutation', () => { describe('when adding ownership to requested user', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Step Error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1674,11 +1485,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: user._key, auth: { @@ -1703,12 +1514,10 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) @@ -1723,11 +1532,12 @@ describe('given the transferOrgOwnership mutation', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), commit: jest.fn().mockRejectedValue(new Error('Commit Error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { transferOrgOwnership ( input: { @@ -1747,11 +1557,11 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: user._key, auth: { @@ -1776,12 +1586,10 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) diff --git a/api/src/affiliation/mutations/__tests__/update-user-role.test.js b/api/src/affiliation/mutations/__tests__/update-user-role.test.js new file mode 100644 index 0000000000..7d455c4e91 --- /dev/null +++ b/api/src/affiliation/mutations/__tests__/update-user-role.test.js @@ -0,0 +1,1485 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' +import { loadUserByUserName, loadUserByKey } from '../../../user/loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('update a users role', () => { + let query, drop, truncate, schema, collections, transaction, i18n, user + + let consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + + const sendRoleChangeEmail = jest.fn() + const orgNames = { + en: 'Treasury Board of Canada Secretariat', + fr: 'Secrétariat du Conseil Trésor du Canada', + } + + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput = [] + }) + + describe('given a successful role update', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + let org, secondaryUser + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + }) + }) + describe('requesting user is a super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + describe('update user from admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: secondaryUser._id, + permission: 'admin', + }) + }) + describe('to super admin', () => { + it('returns status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', org._key)}" + role: SUPER_ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: { load: jest.fn().mockResolvedValue(orgNames) }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const expectedResponse = { + data: { + updateUserRole: { + result: { + status: 'User role was updated successfully.', + user: { + displayName: 'Test Account', + }, + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, + ]) + expect(response).toEqual(expectedResponse) + }) + }) + describe('to user', () => { + it('returns status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', org._key)}" + role: USER + } + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: { load: jest.fn().mockResolvedValue(orgNames) }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const expectedResponse = { + data: { + updateUserRole: { + result: { + status: 'User role was updated successfully.', + user: { + displayName: 'Test Account', + }, + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} successful updated user: ${secondaryUser._key} role to user in org: treasury-board-secretariat.`, + ]) + expect(response).toEqual(expectedResponse) + }) + }) + }) + describe('update user from user', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: secondaryUser._id, + permission: 'user', + }) + }) + describe('to super admin', () => { + it('returns status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', org._key)}" + role: SUPER_ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: { load: jest.fn().mockResolvedValue(orgNames) }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const expectedResponse = { + data: { + updateUserRole: { + result: { + status: 'User role was updated successfully.', + user: { + displayName: 'Test Account', + }, + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, + ]) + expect(response).toEqual(expectedResponse) + }) + }) + describe('to admin', () => { + it('returns status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', org._key)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: { load: jest.fn().mockResolvedValue(orgNames) }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const expectedResponse = { + data: { + updateUserRole: { + result: { + status: 'User role was updated successfully.', + user: { + displayName: 'Test Account', + }, + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, + ]) + expect(response).toEqual(expectedResponse) + }) + }) + }) + }) + describe('requesting user is admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + }) + describe('update user from user', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: secondaryUser._id, + permission: 'user', + }) + }) + describe('to admin', () => { + it('returns status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', org._key)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: { load: jest.fn().mockResolvedValue(orgNames) }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const expectedResponse = { + data: { + updateUserRole: { + result: { + status: 'User role was updated successfully.', + user: { + displayName: 'Test Account', + }, + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, + ]) + expect(response).toEqual(expectedResponse) + }) + it('sends a role update notification email with correct parameters', async () => { + await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', org._key)}" + role: USER + } + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: { load: jest.fn().mockResolvedValue(orgNames) }, + }, + validators: { + cleanseInput, + }, + }, + }) + + expect(sendRoleChangeEmail).toHaveBeenCalledWith({ + user: expect.any(Object), + newRole: 'user', + oldRole: expect.any(String), + orgNames, + }) + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful update', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given an unsuccessful role update', () => { + describe('user attempts to update their own role', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test.account@istio.actually.exists" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn(), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 400, + description: 'Unable to update your own role.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([`User: 123 attempted to update their own role in org: 123.`]) + expect(response).toEqual(error) + }) + }) + describe('user attempts to update a user that does not exist', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "random@email.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: { + load: jest.fn(), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 400, + description: 'Unable to update role: user unknown.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: 123 attempted to update a user: random@email.ca role in org: 123, however there is no user associated with that user name.`, + ]) + expect(response).toEqual(error) + }) + }) + describe('user attempts to update a users role in an org that does not exist', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 1)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + notify: { sendRoleChangeEmail }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 400, + description: 'Unable to update role: organization unknown.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: 123 attempted to update a user: 456 role in org: 1, however there is no org associated with that id.`, + ]) + expect(response).toEqual(error) + }) + }) + describe('requesting user permission is user', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('user'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 403, + description: + 'Permission Denied: Please contact organization admin for help with user role changes.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, + ]) + expect(response).toEqual(error) + }) + }) + describe('user attempts to update a users role in an org that the requesting user does not belong to', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ count: 0 }), + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue(undefined), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 403, + description: + 'Permission Denied: Please contact organization admin for help with user role changes.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, + ]) + expect(response).toEqual(error) + }) + }) + describe('user attempts to update a user that does not belong to the requested org', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ count: 0 }), + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 400, + description: 'Unable to update role: user does not belong to organization.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however that user does not have an affiliation with that organization.`, + ]) + expect(response).toEqual(error) + }) + }) + describe('requesting users role is admin', () => { + describe('requested users role is super admin', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: USER + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ + count: 1, + next: jest.fn().mockReturnValue({ permission: 'super_admin' }), + }), + collections: collectionNames, + transaction: jest.fn(), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = { + data: { + updateUserRole: { + result: { + code: 403, + description: 'Permission Denied: Please contact super admin for help with user role changes.', + }, + }, + }, + } + + expect(consoleOutput).toEqual([ + `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to update a super_admin or assign the user role.`, + ]) + expect(response).toEqual(error) + }) + }) + }) + }) + describe('database error occurs', () => { + describe('when getting current affiliation', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: USER + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockRejectedValue(new Error('database error')), + collections: collectionNames, + transaction: jest.fn(), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] + + expect(consoleOutput).toEqual([ + `Database error occurred when user: 123 attempted to update a user's: 456 role, error: Error: database error`, + ]) + expect(response.errors).toEqual(error) + }) + }) + }) + describe('cursor error occur', () => { + describe('when gathering affiliation info', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: USER + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ + count: 1, + next: jest.fn().mockRejectedValue(new Error('cursor error')), + }), + collections: collectionNames, + transaction: jest.fn(), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] + + expect(consoleOutput).toEqual([ + `Cursor error occurred when user: 123 attempted to update a user's: 456 role, error: Error: cursor error`, + ]) + expect(response.errors).toEqual(error) + }) + }) + }) + describe('transaction error occurs', () => { + describe('when running transaction', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ + count: 1, + next: jest.fn().mockReturnValue({ permission: 'user' }), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx step error'), + abort: jest.fn(), + }), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] + + expect(consoleOutput).toEqual([ + `Transaction step error occurred when user: 123 attempted to update a user's: 456 role, error: trx step error`, + ]) + expect(response.errors).toEqual(error) + }) + }) + describe('when committing transaction', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserRole ( + input: { + userName: "test@email.gc.ca" + orgId: "${toGlobalId('organizations', 123)}" + role: ADMIN + } + ) { + result { + ... on UpdateUserRoleResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ + count: 1, + next: jest.fn().mockReturnValue({ permission: 'user' }), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn(), + commit: jest.fn().mockRejectedValue('trx commit error'), + abort: jest.fn(), + }), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + _key: 456, + }), + }, + }, + notify: { sendRoleChangeEmail }, + validators: { + cleanseInput, + }, + }, + }) + + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] + + expect(consoleOutput).toEqual([ + `Transaction commit error occurred when user: 123 attempted to update a user's: 456 role, error: trx commit error`, + ]) + expect(response.errors).toEqual(error) + }) + }) + }) + }) + }) +}) diff --git a/api/src/affiliation/mutations/index.js b/api/src/affiliation/mutations/index.js new file mode 100644 index 0000000000..557b4f8353 --- /dev/null +++ b/api/src/affiliation/mutations/index.js @@ -0,0 +1,6 @@ +export * from './invite-user-to-org' +export * from './leave-organization' +export * from './remove-user-from-org' +export * from './request-org-affiliation' +export * from './transfer-org-ownership' +export * from './update-user-role' diff --git a/api/src/affiliation/mutations/invite-user-to-org.js b/api/src/affiliation/mutations/invite-user-to-org.js new file mode 100644 index 0000000000..8398c497aa --- /dev/null +++ b/api/src/affiliation/mutations/invite-user-to-org.js @@ -0,0 +1,285 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' + +import { inviteUserToOrgUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { InvitationRoleEnums } from '../../enums' +import ac from '../../access-control' + +export const inviteUserToOrg = new mutationWithClientMutationId({ + name: 'InviteUserToOrg', + description: `This mutation allows admins and higher to invite users to any of their +organizations, if the invited user does not have an account, they will be +able to sign-up and be assigned to that organization in one mutation.`, + inputFields: () => ({ + userName: { + type: new GraphQLNonNull(GraphQLEmailAddress), + description: 'Users email that you would like to invite to your org.', + }, + requestedRole: { + type: new GraphQLNonNull(InvitationRoleEnums), + description: 'The role which you would like this user to have.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The organization you wish to invite the user to.', + }, + }), + outputFields: () => ({ + result: { + type: inviteUserToOrgUnion, + description: + '`InviteUserToOrgUnion` returning either a `InviteUserToOrgResult`, or `InviteUserToOrgError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + request, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, tokenize, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByUserName, loadOrganizationNamesById }, + notify: { sendOrgInviteCreateAccount, sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + ) => { + const userName = cleanseInput(args.userName).toLowerCase() + const requestedRole = cleanseInput(args.requestedRole) + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + // Get requesting user + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Make sure user is not inviting themselves + if (user.userName === userName) { + console.warn(`User: ${userKey} attempted to invite themselves to ${orgId}.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to invite yourself to an org.`), + } + } + + // Check to see if requested org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to invite user: ${userName} to ${orgId} however there is no org associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to invite user to unknown organization.`), + } + } + + // Check to see requesting users permission to the org is + const permission = await checkPermission({ orgId: org._id }) + + // Only admins, owners, and super admins may invite users to an org + if (!ac.can(permission).createOwn('affiliation').granted) { + console.warn( + `User: ${userKey} attempted to invite user: ${userName} to org: ${org._key} with role: ${requestedRole} but does not have permission to do so.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact organization admin for help with user invitations.`), + } + } + + // Only super admins may create owners and other super admins + const privilegedRoles = ac.getRoles().filter((r) => ac.can(r).deleteOwn('organization').granted) + if (privilegedRoles.includes(requestedRole) && !ac.can(permission).createAny('affiliation').granted) { + console.warn( + `User: ${userKey} attempted to invite user: ${userName} to org: ${org._key} with role: ${requestedRole} but does not have permission to do so.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with user invitations.`), + } + } + + // Get org names to use in email + let orgNames + try { + orgNames = await loadOrganizationNamesById.load(org._id) + } catch (err) { + console.error( + `Error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) + } + + // Check to see if requested user exists + const requestedUser = await loadUserByUserName.load(userName) + + // If there is not associated account with that username send invite to org with create account + if (typeof requestedUser === 'undefined') { + const token = tokenize({ + expiresIn: '3d', + parameters: { userName, orgKey: org._key, requestedRole }, + }) + const createAccountLink = `https://${request.get('host')}/create-user/${token}` + + await sendOrgInviteCreateAccount({ + user: { userName: userName }, + orgNameEN: orgNames.orgNameEN, + orgNameFR: orgNames.orgNameFR, + createAccountLink, + }) + + console.info(`User: ${userKey} successfully invited user: ${userName} to the service, and org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'add', + target: { + resource: userName, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'user', // user, org, domain + updatedProperties: [{ name: 'role', oldValue: '', newValue: requestedRole }], + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully sent invitation to service, and organization email.`), + } + } + + // If account is found, check if already affiliated with org + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 INBOUND ${requestedUser._id} affiliations + FILTER e._from == ${org._id} + RETURN e + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, + ) + return { + _type: 'error', + code: 500, + description: i18n._(t`Unable to invite user to organization. Please try again.`), + } + } + + if (affiliationCursor.count > 0) { + // If affiliation is found, return error + console.warn( + `User: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug} however they are already affiliated with that org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to invite user to organization. User is already affiliated with organization.`), + } + } + + // User is not affiliated with org, create affiliation + + // Setup Transaction + const trx = await transaction(collections) + + // Create affiliation + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + INSERT { + _from: ${org._id}, + _to: ${requestedUser._id}, + permission: ${requestedRole}, + } INTO affiliations + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + return { + _type: 'error', + code: 500, + description: i18n._(t`Unable to invite user. Please try again.`), + } + } + + await sendOrgInviteEmail({ + user: requestedUser, + orgNameEN: orgNames.orgNameEN, + orgNameFR: orgNames.orgNameFR, + }) + + // Commit affiliation + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + return { + _type: 'error', + code: 500, + description: i18n._(t`Unable to invite user. Please try again.`), + } + } + + console.info(`User: ${userKey} successfully invited user: ${requestedUser._key} to the org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'add', + target: { + resource: userName, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + updatedProperties: [{ name: 'role', oldValue: '', newValue: requestedRole }], + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully invited user to organization, and sent notification email.`), + } + }, +}) diff --git a/api/src/affiliation/mutations/leave-organization.js b/api/src/affiliation/mutations/leave-organization.js new file mode 100644 index 0000000000..ce8fc78066 --- /dev/null +++ b/api/src/affiliation/mutations/leave-organization.js @@ -0,0 +1,89 @@ +import { t } from '@lingui/macro' +import { GraphQLID, GraphQLNonNull } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' + +import { leaveOrganizationUnion } from '../unions' + +export const leaveOrganization = new mutationWithClientMutationId({ + name: 'LeaveOrganization', + description: 'This mutation allows users to leave a given organization.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'Id of the organization the user is looking to leave.', + }, + }), + outputFields: () => ({ + result: { + type: leaveOrganizationUnion, + description: '`LeaveOrganizationUnion` resolving to either a `LeaveOrganizationResult` or `AffiliationError`.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + auth: { userRequired, verifiedRequired }, + loaders: { loadOrgByKey }, + validators: { cleanseInput }, + }, + ) => { + const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) + + const user = await userRequired() + + verifiedRequired({ user }) + + const org = await loadOrgByKey.load(orgKey) + + if (typeof org === 'undefined') { + console.warn(`User ${user._key} attempted to leave undefined organization: ${orgKey}`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to leave undefined organization.`), + } + } + + // Setup Trans action + const trx = await transaction(collections) + + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations + FILTER e._to == ${user._id} + REMOVE { _key: e._key } IN affiliations + OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing user: ${user._key} affiliation with org: ${org._key}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable leave organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when user: ${user._key} attempted to leave org: ${org._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable leave organization. Please try again.`)) + } + + console.info(`User: ${user._key} successfully left org: ${org.slug}.`) + + return { + _type: 'regular', + status: i18n._(t`Successfully left organization: ${org.slug}`), + } + }, +}) diff --git a/api/src/affiliation/mutations/remove-user-from-org.js b/api/src/affiliation/mutations/remove-user-from-org.js new file mode 100644 index 0000000000..88a780090b --- /dev/null +++ b/api/src/affiliation/mutations/remove-user-from-org.js @@ -0,0 +1,208 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { removeUserFromOrgUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import ac from '../../access-control' + +export const removeUserFromOrg = new mutationWithClientMutationId({ + name: 'RemoveUserFromOrg', + description: 'This mutation allows admins or higher to remove users from any organizations they belong to.', + inputFields: () => ({ + userId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The user id of the user to be removed.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The organization that the user is to be removed from.', + }, + }), + outputFields: () => ({ + result: { + type: removeUserFromOrgUnion, + description: + '`RemoveUserFromOrgUnion` returning either a `RemoveUserFromOrgResult`, or `RemoveUserFromOrgError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByKey }, + validators: { cleanseInput }, + }, + ) => { + // Cleanse Input + const { id: requestedUserKey } = fromGlobalId(cleanseInput(args.userId)) + const { id: requestedOrgKey } = fromGlobalId(cleanseInput(args.orgId)) + + // Get requesting user + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Get requested org + const requestedOrg = await loadOrgByKey.load(requestedOrgKey) + if (typeof requestedOrg === 'undefined') { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrgKey}, however no org with that id could be found.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove user from unknown organization.`), + } + } + + // Check requesting users permission + const permission = await checkPermission({ orgId: requestedOrg._id }) + + // Get requested user + const requestedUser = await loadUserByKey.load(requestedUserKey) + if (typeof requestedUser === 'undefined') { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrg._key}, however no user with that id could be found.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove unknown user from organization.`), + } + } + + // Get requested users current permission level + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 ANY ${requestedUser._id} affiliations + FILTER e._from == ${requestedOrg._id} + RETURN { _key: e._key, permission: e.permission } + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to check the current permission of user: ${requestedUser._key} to see if they could be removed: ${err}`, + ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) + } + + if (affiliationCursor.count < 1) { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUser._key}, but they do not have any affiliations to org: ${requestedOrg._key}.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove a user that already does not belong to this organization.`), + } + } + + let affiliation + try { + affiliation = await affiliationCursor.next() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to check the current permission of user: ${requestedUser._key} to see if they could be removed: ${err}`, + ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) + } + + // Only admins, owners, and super admins can remove users + if (!ac.can(permission).deleteOwn('affiliation').granted) { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, but they do not have the right permission.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing users.`), + } + } + + // Only super admins can remove super admins and owners + if (['owner', 'super_admin'].includes(affiliation.permission) && permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, but they do not have the right permission.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing users.`), + } + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + FOR aff IN affiliations + FILTER aff._from == ${requestedOrg._id} + FILTER aff._to == ${requestedUser._id} + REMOVE aff IN affiliations + RETURN true + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error( + `Trx commit error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) + } + + console.info(`User: ${userKey} successfully removed user: ${requestedUser._key} from org: ${requestedOrg._key}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'remove', + target: { + resource: requestedUser.userName, + organization: { + id: requestedOrg._key, + name: requestedOrg.name, + }, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully removed user from organization.`), + user: { + id: requestedUser.id, + userName: requestedUser.userName, + }, + } + }, +}) diff --git a/api/src/affiliation/mutations/request-org-affiliation.js b/api/src/affiliation/mutations/request-org-affiliation.js new file mode 100644 index 0000000000..c38b6b8161 --- /dev/null +++ b/api/src/affiliation/mutations/request-org-affiliation.js @@ -0,0 +1,234 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { inviteUserToOrgUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' + +const { SERVICE_ACCOUNT_EMAIL } = process.env + +export const requestOrgAffiliation = new mutationWithClientMutationId({ + name: 'RequestOrgAffiliation', + description: `This mutation allows users to request to join an organization.`, + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The organization you wish to invite the user to.', + }, + }), + outputFields: () => ({ + result: { + type: inviteUserToOrgUnion, + description: + '`InviteUserToOrgUnion` returning either a `InviteUserToOrgResult`, or `InviteUserToOrgError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + request, + collections, + transaction, + userKey, + request: { ip }, + auth: { userRequired, verifiedRequired }, + loaders: { loadOrgByKey, loadUserByKey, loadOrganizationNamesById }, + notify: { sendInviteRequestEmail }, + validators: { cleanseInput }, + }, + ) => { + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + // Get requesting user + const user = await userRequired() + verifiedRequired({ user }) + + // Check to see if requested org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to request invite to org: ${orgId} however there is no org associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to request invite to unknown organization.`), + } + } + + // Check to see if user is already a member of the org + let affiliationCursor + try { + affiliationCursor = await query` + FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations + FILTER e._to == ${user._id} + RETURN e + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to request invite to ${orgId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + if (affiliationCursor.count > 0) { + const requestedAffiliation = await affiliationCursor.next() + if (requestedAffiliation.permission === 'pending') { + console.warn( + `User: ${userKey} attempted to request invite to org: ${orgId} however they have already requested to join that org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._( + t`Unable to request invite to organization with which you have already requested to join.`, + ), + } + } else { + console.warn( + `User: ${userKey} attempted to request invite to org: ${orgId} however they are already affiliated with that org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to request invite to organization with which you are already affiliated.`), + } + } + } + + // Setup Transaction + const trx = await transaction(collections) + + // Create pending affiliation + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + INSERT { + _from: ${org._id}, + _to: ${user._id}, + permission: "pending", + } INTO affiliations + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred while user: ${userKey} attempted to request invite to org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + // get all org admins + let orgAdminsCursor + try { + orgAdminsCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations + FILTER e.permission IN ["admin", "owner", "super_admin"] + RETURN v._key + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to request invite to ${orgId}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + let orgAdmins + try { + orgAdmins = await orgAdminsCursor.all() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to request invite to ${orgId}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + if (typeof SERVICE_ACCOUNT_EMAIL !== 'undefined') orgAdmins.push('service-account') + + if (orgAdmins.length > 0) { + // Get org names to use in email + let orgNames + try { + orgNames = await loadOrganizationNamesById.load(org._id) + } catch (err) { + console.error( + `Error occurred when user: ${userKey} attempted to request invite to org: ${org._key}. Error while retrieving organization names. error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + const adminLink = `https://${request.get('host')}/admin/organizations` + // send notification to org admins + for (const userKey of orgAdmins) { + let adminUser + if (userKey === 'service-account') { + adminUser = { + userName: SERVICE_ACCOUNT_EMAIL, + displayName: 'Service Account', + _key: 'service-account', + } + } else adminUser = await loadUserByKey.load(userKey) + + await sendInviteRequestEmail({ + user: adminUser, + orgNameEN: orgNames.orgNameEN, + orgNameFR: orgNames.orgNameFR, + adminLink, + }) + } + } + + // Commit Transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred while user: ${userKey} attempted to request invite to org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + console.info(`User: ${userKey} successfully requested invite to the org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + ipAddress: ip, + }, + action: 'add', + target: { + resource: user.userName, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + updatedProperties: [ + { + name: 'permission', + oldValue: null, + newValue: 'pending', + }, + ], + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully requested invite to organization, and sent notification email.`), + } + }, +}) diff --git a/api/src/affiliation/mutations/transfer-org-ownership.js b/api/src/affiliation/mutations/transfer-org-ownership.js new file mode 100644 index 0000000000..3f990982bc --- /dev/null +++ b/api/src/affiliation/mutations/transfer-org-ownership.js @@ -0,0 +1,191 @@ +import { t } from '@lingui/macro' +import { GraphQLID, GraphQLNonNull } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' + +import { transferOrgOwnershipUnion } from '../unions' + +export const transferOrgOwnership = new mutationWithClientMutationId({ + name: 'TransferOrgOwnership', + description: 'This mutation allows a user to transfer org ownership to another user in the given org.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'Id of the organization the user is looking to transfer ownership of.', + }, + userId: { + type: new GraphQLNonNull(GraphQLID), + description: 'Id of the user that the org ownership is being transferred to.', + }, + }), + outputFields: () => ({ + result: { + type: transferOrgOwnershipUnion, + description: + '`TransferOrgOwnershipUnion` resolving to either a `TransferOrgOwnershipResult` or `AffiliationError`.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + auth: { checkOrgOwner, userRequired, verifiedRequired }, + loaders: { loadOrgByKey, loadUserByKey }, + validators: { cleanseInput }, + }, + ) => { + // cleanse inputs + const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) + const { id: userTransferKey } = fromGlobalId(cleanseInput(args.userId)) + + // protect mutation from un-authed users + const requestingUser = await userRequired() + + // ensure that user has email verified their account + verifiedRequired({ user: requestingUser }) + + // load the requested org + const org = await loadOrgByKey.load(orgKey) + + // ensure requested org is not undefined + if (typeof org === 'undefined') { + console.warn(`User: ${requestingUser._key} attempted to transfer org ownership of an undefined org.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to transfer ownership of undefined organization.`), + } + } + + // get org owner bool value + const owner = await checkOrgOwner({ orgId: org._id }) + + // check to see if requesting user is the org owner + if (!owner) { + console.warn( + `User: ${requestingUser._key} attempted to transfer org: ${org.slug} ownership but does not have current ownership.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact org owner to transfer ownership.`), + } + } + + // get the user that is org ownership is being transferred to + const requestedUser = await loadUserByKey.load(userTransferKey) + + // check to ensure requested user is not undefined + if (typeof requestedUser === 'undefined') { + console.warn( + `User: ${requestingUser._key} attempted to transfer org: ${org.slug} ownership to an undefined user.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to transfer ownership of an org to an undefined user.`), + } + } + + // query db for requested user affiliation to org + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations + FILTER e._to == ${requestedUser._id} + RETURN e + ` + } catch (err) { + console.error( + `Database error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, + ) + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) + } + + // check to see if requested user belongs to org + if (affiliationCursor.count < 1) { + console.warn( + `User: ${requestingUser._key} attempted to transfer org: ${org.slug} ownership to user: ${requestedUser._key} but they are not affiliated with the org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._( + t`Unable to transfer ownership to a user outside the org. Please invite the user and try again.`, + ), + } + } + + // Setup Trans action + const trx = await transaction(collections) + + // remove current org owners role + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + FOR aff IN affiliations + FILTER aff._from == ${org._id} + FILTER aff._to == ${requestingUser._id} + UPDATE { _key: aff._key } WITH { + permission: "admin", + } IN affiliations + RETURN aff + `, + ) + } catch (err) { + console.error( + `Trx step error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) + } + + // set new org owner + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + FOR aff IN affiliations + FILTER aff._from == ${org._id} + FILTER aff._to == ${requestedUser._id} + UPDATE { _key: aff._key } WITH { + permission: "owner", + } IN affiliations + RETURN aff + `, + ) + } catch (err) { + console.error( + `Trx step error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) + } + + // commit changes to the db + try { + await trx.commit() + } catch (err) { + console.error( + `Trx commit error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) + } + + console.info( + `User: ${requestingUser._key} successfully transfer org: ${org.slug} ownership to user: ${requestedUser._key}.`, + ) + return { + _type: 'regular', + status: i18n._(t`Successfully transferred org: ${org.slug} ownership to user: ${requestedUser.userName}`), + } + }, +}) diff --git a/api/src/affiliation/mutations/update-user-role.js b/api/src/affiliation/mutations/update-user-role.js new file mode 100644 index 0000000000..1f92a58a5b --- /dev/null +++ b/api/src/affiliation/mutations/update-user-role.js @@ -0,0 +1,257 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' + +import { RoleEnums } from '../../enums' +import { updateUserRoleUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import ac from '../../access-control' + +export const updateUserRole = new mutationWithClientMutationId({ + name: 'UpdateUserRole', + description: `This mutation allows super admins, and admins of the given organization to +update the permission level of a given user that already belongs to the +given organization.`, + inputFields: () => ({ + userName: { + type: new GraphQLNonNull(GraphQLEmailAddress), + description: 'The username of the user you wish to update their role to.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The organization that the admin, and the user both belong to.', + }, + role: { + type: new GraphQLNonNull(RoleEnums), + description: 'The role that the admin wants to give to the selected user.', + }, + }), + outputFields: () => ({ + result: { + type: updateUserRoleUnion, + description: '`UpdateUserRoleUnion` returning either a `UpdateUserRoleResult`, or `UpdateUserRoleError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByUserName, loadOrganizationNamesById }, + validators: { cleanseInput }, + notify: { sendRoleChangeEmail }, + }, + ) => { + // Cleanse Input + const userName = cleanseInput(args.userName).toLowerCase() + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + const role = cleanseInput(args.role) + + // Get requesting user from db + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Make sure user is not attempting to update their own role + if (user.userName === userName) { + console.warn(`User: ${userKey} attempted to update their own role in org: ${orgId}.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update your own role.`), + } + } + + // Check to see if requested user exists + const requestedUser = await loadUserByUserName.load(userName) + + if (typeof requestedUser === 'undefined') { + console.warn( + `User: ${userKey} attempted to update a user: ${userName} role in org: ${orgId}, however there is no user associated with that user name.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update role: user unknown.`), + } + } + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${orgId}, however there is no org associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update role: organization unknown.`), + } + } + + // Check requesting user's permission + const permission = await checkPermission({ orgId: org._id }) + + // Only admins, owners, and super admins can update a user's role + if (!ac.can(permission).updateOwn('affiliation').granted) { + console.warn( + `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact organization admin for help with user role changes.`), + } + } + + // Get user's current permission level + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 ANY ${requestedUser._id} affiliations + FILTER e._from == ${org._id} + RETURN { _key: e._key, permission: e.permission } + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, + ) + + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) + } + + if (affiliationCursor.count < 1) { + console.warn( + `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however that user does not have an affiliation with that organization.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update role: user does not belong to organization.`), + } + } + + let affiliation + try { + affiliation = await affiliationCursor.next() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, + ) + + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) + } + + // Only super admins can update or assign privileged roles (those with org-level authority) + const privilegedRoles = ac.getRoles().filter((r) => ac.can(r).deleteOwn('organization').granted) + if ( + (privilegedRoles.includes(affiliation.permission) || privilegedRoles.includes(role)) && + !ac.can(permission).updateAny('affiliation').granted + ) { + console.warn( + `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to update a ${affiliation.permission} or assign the ${role} role.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with user role changes.`), + } + } + + // Only super admins can create new super admins + const edge = { + _from: org._id, + _to: requestedUser._id, + permission: role, + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step(async () => { + await query` + WITH affiliations, organizations, users + UPSERT { _key: ${affiliation._key} } + INSERT ${edge} + UPDATE ${edge} + IN affiliations + ` + }) + } catch (err) { + console.error( + `Transaction step error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.warn( + `Transaction commit error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) + } + + // Get org names to use in email + let orgNames + try { + orgNames = await loadOrganizationNamesById.load(org._id) + } catch (err) { + console.error( + `Error occurred when user: ${userKey} attempted to update a user's: ${userName} role. Error while retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) + } + + await sendRoleChangeEmail({ user: requestedUser, newRole: role, oldRole: affiliation.permission, orgNames }) + + console.info(`User: ${userKey} successful updated user: ${requestedUser._key} role to ${role} in org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: userName, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'user', // user, org, domain + updatedProperties: [ + { + name: 'userRole', + oldValue: affiliation.permission, + newValue: role, + }, + ], + }, + }) + + return { + _type: 'regular', + status: i18n._(t`User role was updated successfully.`), + user: requestedUser, + } + }, +}) diff --git a/api-js/src/affiliation/objects/__tests__/affiliation-connection.test.js b/api/src/affiliation/objects/__tests__/affiliation-connection.test.js similarity index 77% rename from api-js/src/affiliation/objects/__tests__/affiliation-connection.test.js rename to api/src/affiliation/objects/__tests__/affiliation-connection.test.js index d5d74fe064..313f98ebb6 100644 --- a/api-js/src/affiliation/objects/__tests__/affiliation-connection.test.js +++ b/api/src/affiliation/objects/__tests__/affiliation-connection.test.js @@ -1,5 +1,5 @@ -import { GraphQLInt } from 'graphql' -import { affiliationConnection } from '../affiliation-connection' +import {GraphQLInt} from 'graphql' +import {affiliationConnection} from '../affiliation-connection' describe('given the affiliation connection object', () => { describe('testing its field definitions', () => { @@ -15,7 +15,7 @@ describe('given the affiliation connection object', () => { it('returns the resolved value', () => { const demoType = affiliationConnection.connectionType.getFields() - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) + expect(demoType.totalCount.resolve({totalCount: 1})).toEqual(1) }) }) }) diff --git a/api-js/src/affiliation/objects/__tests__/affiliation-error.test.js b/api/src/affiliation/objects/__tests__/affiliation-error.test.js similarity index 80% rename from api-js/src/affiliation/objects/__tests__/affiliation-error.test.js rename to api/src/affiliation/objects/__tests__/affiliation-error.test.js index 0ece85fcbd..d37dbde3d1 100644 --- a/api-js/src/affiliation/objects/__tests__/affiliation-error.test.js +++ b/api/src/affiliation/objects/__tests__/affiliation-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { affiliationError } from '../affiliation-error' +import {affiliationError} from '../affiliation-error' describe('given the affiliationError object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the affiliationError object', () => { it('returns the resolved field', () => { const demoType = affiliationError.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the affiliationError object', () => { const demoType = affiliationError.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/affiliation/objects/__tests__/affiliation.test.js b/api/src/affiliation/objects/__tests__/affiliation.test.js similarity index 93% rename from api-js/src/affiliation/objects/__tests__/affiliation.test.js rename to api/src/affiliation/objects/__tests__/affiliation.test.js index 4a09f67661..ea7daa0d19 100644 --- a/api-js/src/affiliation/objects/__tests__/affiliation.test.js +++ b/api/src/affiliation/objects/__tests__/affiliation.test.js @@ -12,7 +12,7 @@ describe('given the user affiliation object', () => { const demoType = affiliationType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a permission field', () => { const demoType = affiliationType.getFields() @@ -39,18 +39,14 @@ describe('given the user affiliation object', () => { it('returns the resolved value', () => { const demoType = affiliationType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('affiliation', '1'), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('affiliation', '1')) }) }) describe('testing the permission resolver', () => { it('returns the resolved value', () => { const demoType = affiliationType.getFields() - expect(demoType.permission.resolve({ permission: 'admin' })).toEqual( - 'admin', - ) + expect(demoType.permission.resolve({ permission: 'admin' })).toEqual('admin') }) }) describe('testing the user resolver', () => { @@ -65,7 +61,6 @@ describe('given the user affiliation object', () => { id: '1', displayName: 'Test Account', emailValidated: false, - preferredLang: 'french', tfaValidated: false, userName: 'test.account@istio.actually.exists', } diff --git a/api-js/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js b/api/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js similarity index 75% rename from api-js/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js rename to api/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js index 2d470db76f..bbed55cfd1 100644 --- a/api-js/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js +++ b/api/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { inviteUserToOrgResultType } from '../invite-user-to-org-result' +import {inviteUserToOrgResultType} from '../invite-user-to-org-result' describe('given the inviteUserToOrgResultType object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the inviteUserToOrgResultType object', () => { it('returns the resolved field', () => { const demoType = inviteUserToOrgResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) }) diff --git a/api-js/src/affiliation/objects/__tests__/leave-organization-result.test.js b/api/src/affiliation/objects/__tests__/leave-organization-result.test.js similarity index 75% rename from api-js/src/affiliation/objects/__tests__/leave-organization-result.test.js rename to api/src/affiliation/objects/__tests__/leave-organization-result.test.js index d90a0b3770..7cfe917a3f 100644 --- a/api-js/src/affiliation/objects/__tests__/leave-organization-result.test.js +++ b/api/src/affiliation/objects/__tests__/leave-organization-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { leaveOrganizationResultType } from '../leave-organization-result' +import {leaveOrganizationResultType} from '../leave-organization-result' describe('given the leaveOrganizationResultType object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the leaveOrganizationResultType object', () => { it('returns the resolved field', () => { const demoType = leaveOrganizationResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) }) diff --git a/api/src/affiliation/objects/__tests__/remove-user-from-org-result.test.js b/api/src/affiliation/objects/__tests__/remove-user-from-org-result.test.js new file mode 100644 index 0000000000..11d972beb6 --- /dev/null +++ b/api/src/affiliation/objects/__tests__/remove-user-from-org-result.test.js @@ -0,0 +1,39 @@ +import {GraphQLString} from 'graphql' + +import {removeUserFromOrgResultType} from '../remove-user-from-org-result' +import {userSharedType} from '../../../user/objects' + +describe('given the removeUserFromOrgResultType object', () => { + describe('testing the field definitions', () => { + it('has an status field', () => { + const demoType = removeUserFromOrgResultType.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + it('has a user field', () => { + const demoType = removeUserFromOrgResultType.getFields() + + expect(demoType).toHaveProperty('user') + expect(demoType.user.type).toMatchObject(userSharedType) + }) + }) + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = removeUserFromOrgResultType.getFields() + + expect(demoType.status.resolve({status: 'status'})).toEqual('status') + }) + }) + describe('testing the user resolver', () => { + it('returns the resolved field', () => { + const demoType = removeUserFromOrgResultType.getFields() + + expect( + demoType.user.resolve({user: {id: 1, userName: 'test@email.ca'}}), + ).toEqual({id: 1, userName: 'test@email.ca'}) + }) + }) + }) +}) diff --git a/api/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js b/api/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js new file mode 100644 index 0000000000..6398cbbfe8 --- /dev/null +++ b/api/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js @@ -0,0 +1,24 @@ +import {GraphQLString} from 'graphql' + +import {transferOrgOwnershipResult} from '../transfer-org-ownership-result' + +describe('given the transferOrgOwnershipResult object', () => { + describe('testing the field definitions', () => { + it('has an status field', () => { + const demoType = transferOrgOwnershipResult.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = transferOrgOwnershipResult.getFields() + + expect(demoType.status.resolve({status: 'status'})).toEqual('status') + }) + }) + }) +}) diff --git a/api-js/src/affiliation/objects/__tests__/update-user-role-result.test.js b/api/src/affiliation/objects/__tests__/update-user-role-result.test.js similarity index 95% rename from api-js/src/affiliation/objects/__tests__/update-user-role-result.test.js rename to api/src/affiliation/objects/__tests__/update-user-role-result.test.js index 75f3822744..e4482a6f64 100644 --- a/api-js/src/affiliation/objects/__tests__/update-user-role-result.test.js +++ b/api/src/affiliation/objects/__tests__/update-user-role-result.test.js @@ -39,14 +39,11 @@ describe('given the updateUserRoleResultType object', () => { id: '1', displayName: 'Test Account', emailValidated: false, - preferredLang: 'french', tfaValidated: false, userName: 'test.account@istio.actually.exists', } - expect(demoType.user.resolve({ user: expectedResult })).toEqual( - expectedResult, - ) + expect(demoType.user.resolve({ user: expectedResult })).toEqual(expectedResult) }) }) }) diff --git a/api/src/affiliation/objects/affiliation-connection.js b/api/src/affiliation/objects/affiliation-connection.js new file mode 100644 index 0000000000..125614258c --- /dev/null +++ b/api/src/affiliation/objects/affiliation-connection.js @@ -0,0 +1,15 @@ +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' +import {affiliationType} from './affiliation' + +export const affiliationConnection = connectionDefinitions({ + name: 'Affiliation', + nodeType: affiliationType, + connectionFields: () => ({ + totalCount: { + type: GraphQLInt, + description: 'The total amount of affiliations the user has access to.', + resolve: ({totalCount}) => totalCount, + }, + }), +}) diff --git a/api-js/src/affiliation/objects/affiliation-error.js b/api/src/affiliation/objects/affiliation-error.js similarity index 75% rename from api-js/src/affiliation/objects/affiliation-error.js rename to api/src/affiliation/objects/affiliation-error.js index 7ce345a5c1..118b33499d 100644 --- a/api-js/src/affiliation/objects/affiliation-error.js +++ b/api/src/affiliation/objects/affiliation-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const affiliationError = new GraphQLObjectType({ name: 'AffiliationError', @@ -8,12 +8,12 @@ export const affiliationError = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/affiliation/objects/affiliation.js b/api/src/affiliation/objects/affiliation.js new file mode 100644 index 0000000000..e1b0f34f6c --- /dev/null +++ b/api/src/affiliation/objects/affiliation.js @@ -0,0 +1,42 @@ +import {GraphQLObjectType} from 'graphql' +import {globalIdField} from 'graphql-relay' + +import {RoleEnums} from '../../enums' +import {organizationType} from '../../organization/objects' +import {userSharedType} from '../../user/objects' +import {nodeInterface} from '../../node' + +export const affiliationType = new GraphQLObjectType({ + name: 'Affiliation', + fields: () => ({ + id: globalIdField('affiliation'), + permission: { + type: RoleEnums, + description: "User's level of access to a given organization.", + resolve: ({permission}) => permission, + }, + user: { + type: userSharedType, + description: 'The affiliated users information.', + resolve: async ({_to}, _args, {loaders: {loadUserByKey}}) => { + const userKey = _to.split('/')[1] + const user = await loadUserByKey.load(userKey) + user.id = user._key + return user + }, + }, + organization: { + type: organizationType, + description: 'The affiliated organizations information.', + resolve: async ({_from}, _args, {loaders: {loadOrgByKey}}) => { + const orgKey = _from.split('/')[1] + const org = await loadOrgByKey.load(orgKey) + org.id = org._key + return org + }, + }, + }), + interfaces: [nodeInterface], + description: + 'User Affiliations containing the permission level for the given organization, the users information, and the organizations information.', +}) diff --git a/api-js/src/affiliation/objects/index.js b/api/src/affiliation/objects/index.js similarity index 100% rename from api-js/src/affiliation/objects/index.js rename to api/src/affiliation/objects/index.js diff --git a/api-js/src/affiliation/objects/invite-user-to-org-result.js b/api/src/affiliation/objects/invite-user-to-org-result.js similarity index 78% rename from api-js/src/affiliation/objects/invite-user-to-org-result.js rename to api/src/affiliation/objects/invite-user-to-org-result.js index 4c28896b5c..558ee56492 100644 --- a/api-js/src/affiliation/objects/invite-user-to-org-result.js +++ b/api/src/affiliation/objects/invite-user-to-org-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const inviteUserToOrgResultType = new GraphQLObjectType({ name: 'InviteUserToOrgResult', @@ -9,7 +9,7 @@ export const inviteUserToOrgResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if the invite or invite email was successfully sent.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), }) diff --git a/api-js/src/affiliation/objects/leave-organization-result.js b/api/src/affiliation/objects/leave-organization-result.js similarity index 78% rename from api-js/src/affiliation/objects/leave-organization-result.js rename to api/src/affiliation/objects/leave-organization-result.js index e125ae2ac7..0bd5bc7006 100644 --- a/api-js/src/affiliation/objects/leave-organization-result.js +++ b/api/src/affiliation/objects/leave-organization-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const leaveOrganizationResultType = new GraphQLObjectType({ name: 'LeaveOrganizationResult', @@ -8,7 +8,7 @@ export const leaveOrganizationResultType = new GraphQLObjectType({ status: { type: GraphQLString, description: 'Status message confirming the user left the org.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), }) diff --git a/api/src/affiliation/objects/remove-user-from-org-result.js b/api/src/affiliation/objects/remove-user-from-org-result.js new file mode 100644 index 0000000000..c3f354dda0 --- /dev/null +++ b/api/src/affiliation/objects/remove-user-from-org-result.js @@ -0,0 +1,20 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' + +import {userSharedType} from '../../user/objects' + +export const removeUserFromOrgResultType = new GraphQLObjectType({ + name: 'RemoveUserFromOrgResult', + description: 'This object is used to inform the user of the removal status.', + fields: () => ({ + status: { + type: GraphQLString, + description: 'Informs the user if the user was successfully removed.', + resolve: ({status}) => status, + }, + user: { + type: userSharedType, + description: 'The user that was just removed.', + resolve: ({user}) => user, + }, + }), +}) diff --git a/api-js/src/affiliation/objects/transfer-org-ownership-result.js b/api/src/affiliation/objects/transfer-org-ownership-result.js similarity index 80% rename from api-js/src/affiliation/objects/transfer-org-ownership-result.js rename to api/src/affiliation/objects/transfer-org-ownership-result.js index 6df1ef5d74..126525b9cd 100644 --- a/api-js/src/affiliation/objects/transfer-org-ownership-result.js +++ b/api/src/affiliation/objects/transfer-org-ownership-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const transferOrgOwnershipResult = new GraphQLObjectType({ name: 'TransferOrgOwnershipResult', @@ -9,7 +9,7 @@ export const transferOrgOwnershipResult = new GraphQLObjectType({ type: GraphQLString, description: 'Status message confirming the user transferred ownership of the org.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), }) diff --git a/api/src/affiliation/objects/update-user-role-result.js b/api/src/affiliation/objects/update-user-role-result.js new file mode 100644 index 0000000000..dae75748dc --- /dev/null +++ b/api/src/affiliation/objects/update-user-role-result.js @@ -0,0 +1,21 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userSharedType} from '../../user/objects' + +export const updateUserRoleResultType = new GraphQLObjectType({ + name: 'UpdateUserRoleResult', + description: + 'This object is used to inform the user of the status of the role update.', + fields: () => ({ + status: { + type: GraphQLString, + description: + "Informs the user if the user who's role was successfully updated.", + resolve: ({status}) => status, + }, + user: { + type: userSharedType, + description: "The user who's role was successfully updated.", + resolve: ({user}) => user, + }, + }), +}) diff --git a/api-js/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js b/api/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js similarity index 76% rename from api-js/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js rename to api/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js index 86d8d0dff0..bfb8836b8e 100644 --- a/api-js/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js +++ b/api/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js @@ -1,7 +1,4 @@ -import { - affiliationError, - inviteUserToOrgResultType, -} from '../../objects/index' +import { affiliationError, inviteUserToOrgResultType } from '../../objects/index' import { inviteUserToOrgUnion } from '../invite-user-to-org-union' describe('given the inviteUserToOrgUnion', () => { @@ -25,9 +22,7 @@ describe('given the inviteUserToOrgUnion', () => { authResult: {}, } - expect(inviteUserToOrgUnion.resolveType(obj)).toMatchObject( - inviteUserToOrgResultType, - ) + expect(inviteUserToOrgUnion.resolveType(obj)).toMatch(inviteUserToOrgResultType.name) }) }) describe('testing the affiliationError', () => { @@ -39,9 +34,7 @@ describe('given the inviteUserToOrgUnion', () => { description: 'text', } - expect(inviteUserToOrgUnion.resolveType(obj)).toMatchObject( - affiliationError, - ) + expect(inviteUserToOrgUnion.resolveType(obj)).toMatch(affiliationError.name) }) }) }) diff --git a/api-js/src/affiliation/unions/__tests__/leave-organization-union.test.js b/api/src/affiliation/unions/__tests__/leave-organization-union.test.js similarity index 76% rename from api-js/src/affiliation/unions/__tests__/leave-organization-union.test.js rename to api/src/affiliation/unions/__tests__/leave-organization-union.test.js index 1167012309..d4d47a28a3 100644 --- a/api-js/src/affiliation/unions/__tests__/leave-organization-union.test.js +++ b/api/src/affiliation/unions/__tests__/leave-organization-union.test.js @@ -1,7 +1,4 @@ -import { - affiliationError, - leaveOrganizationResultType, -} from '../../objects/index' +import { affiliationError, leaveOrganizationResultType } from '../../objects/index' import { leaveOrganizationUnion } from '../leave-organization-union' describe('given the leaveOrganizationUnion', () => { @@ -25,9 +22,7 @@ describe('given the leaveOrganizationUnion', () => { authResult: {}, } - expect(leaveOrganizationUnion.resolveType(obj)).toMatchObject( - leaveOrganizationResultType, - ) + expect(leaveOrganizationUnion.resolveType(obj)).toMatch(leaveOrganizationResultType.name) }) }) describe('testing the affiliationError', () => { @@ -39,9 +34,7 @@ describe('given the leaveOrganizationUnion', () => { description: 'text', } - expect(leaveOrganizationUnion.resolveType(obj)).toMatchObject( - affiliationError, - ) + expect(leaveOrganizationUnion.resolveType(obj)).toMatch(affiliationError.name) }) }) }) diff --git a/api-js/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js b/api/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js similarity index 83% rename from api-js/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js rename to api/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js index dbba78f6ce..86ca9c1187 100644 --- a/api-js/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js +++ b/api/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js @@ -22,9 +22,7 @@ describe('given the removeUserFromOrgUnion', () => { authResult: {}, } - expect(removeUserFromOrgUnion.resolveType(obj)).toMatchObject( - removeUserFromOrgResultType, - ) + expect(removeUserFromOrgUnion.resolveType(obj)).toMatch(removeUserFromOrgResultType.name) }) }) describe('testing the affiliationError', () => { @@ -36,9 +34,7 @@ describe('given the removeUserFromOrgUnion', () => { description: 'text', } - expect(removeUserFromOrgUnion.resolveType(obj)).toMatchObject( - affiliationError, - ) + expect(removeUserFromOrgUnion.resolveType(obj)).toMatch(affiliationError.name) }) }) }) diff --git a/api-js/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js b/api/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js similarity index 85% rename from api-js/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js rename to api/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js index ca9f3603d0..a6a7616195 100644 --- a/api-js/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js +++ b/api/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js @@ -1,7 +1,4 @@ -import { - affiliationError, - transferOrgOwnershipResult, -} from '../../objects/index' +import { affiliationError, transferOrgOwnershipResult } from '../../objects/index' import { transferOrgOwnershipUnion } from '../transfer-org-ownership-union' describe('given the transferOrgOwnershipUnion', () => { @@ -25,9 +22,7 @@ describe('given the transferOrgOwnershipUnion', () => { authResult: {}, } - expect(transferOrgOwnershipUnion.resolveType(obj)).toMatchObject( - transferOrgOwnershipResult, - ) + expect(transferOrgOwnershipUnion.resolveType(obj)).toMatch(transferOrgOwnershipResult.name) }) }) describe('testing the affiliationError', () => { @@ -39,9 +34,7 @@ describe('given the transferOrgOwnershipUnion', () => { description: 'text', } - expect(transferOrgOwnershipUnion.resolveType(obj)).toMatchObject( - affiliationError, - ) + expect(transferOrgOwnershipUnion.resolveType(obj)).toMatch(affiliationError.name) }) }) }) diff --git a/api-js/src/affiliation/unions/__tests__/update-user-role-union.test.js b/api/src/affiliation/unions/__tests__/update-user-role-union.test.js similarity index 83% rename from api-js/src/affiliation/unions/__tests__/update-user-role-union.test.js rename to api/src/affiliation/unions/__tests__/update-user-role-union.test.js index 7fcee8d466..035d76a207 100644 --- a/api-js/src/affiliation/unions/__tests__/update-user-role-union.test.js +++ b/api/src/affiliation/unions/__tests__/update-user-role-union.test.js @@ -22,9 +22,7 @@ describe('given the updateUserRoleUnion', () => { authResult: {}, } - expect(updateUserRoleUnion.resolveType(obj)).toMatchObject( - updateUserRoleResultType, - ) + expect(updateUserRoleUnion.resolveType(obj)).toMatch(updateUserRoleResultType.name) }) }) describe('testing the affiliationError', () => { @@ -36,9 +34,7 @@ describe('given the updateUserRoleUnion', () => { description: 'text', } - expect(updateUserRoleUnion.resolveType(obj)).toMatchObject( - affiliationError, - ) + expect(updateUserRoleUnion.resolveType(obj)).toMatch(affiliationError.name) }) }) }) diff --git a/api-js/src/affiliation/unions/index.js b/api/src/affiliation/unions/index.js similarity index 100% rename from api-js/src/affiliation/unions/index.js rename to api/src/affiliation/unions/index.js diff --git a/api-js/src/affiliation/unions/invite-user-to-org-union.js b/api/src/affiliation/unions/invite-user-to-org-union.js similarity index 86% rename from api-js/src/affiliation/unions/invite-user-to-org-union.js rename to api/src/affiliation/unions/invite-user-to-org-union.js index ed17c8be65..ec5a8e5836 100644 --- a/api-js/src/affiliation/unions/invite-user-to-org-union.js +++ b/api/src/affiliation/unions/invite-user-to-org-union.js @@ -8,9 +8,9 @@ export const inviteUserToOrgUnion = new GraphQLUnionType({ types: [affiliationError, inviteUserToOrgResultType], resolveType({ _type }) { if (_type === 'regular') { - return inviteUserToOrgResultType + return inviteUserToOrgResultType.name } else { - return affiliationError + return affiliationError.name } }, }) diff --git a/api-js/src/affiliation/unions/leave-organization-union.js b/api/src/affiliation/unions/leave-organization-union.js similarity index 86% rename from api-js/src/affiliation/unions/leave-organization-union.js rename to api/src/affiliation/unions/leave-organization-union.js index 79a27e0249..91b3c04872 100644 --- a/api-js/src/affiliation/unions/leave-organization-union.js +++ b/api/src/affiliation/unions/leave-organization-union.js @@ -9,9 +9,9 @@ export const leaveOrganizationUnion = new GraphQLUnionType({ types: [affiliationError, leaveOrganizationResultType], resolveType({ _type }) { if (_type === 'regular') { - return leaveOrganizationResultType + return leaveOrganizationResultType.name } else { - return affiliationError + return affiliationError.name } }, }) diff --git a/api-js/src/affiliation/unions/remove-user-from-org-union.js b/api/src/affiliation/unions/remove-user-from-org-union.js similarity index 86% rename from api-js/src/affiliation/unions/remove-user-from-org-union.js rename to api/src/affiliation/unions/remove-user-from-org-union.js index da7486adc5..a389a8aa95 100644 --- a/api-js/src/affiliation/unions/remove-user-from-org-union.js +++ b/api/src/affiliation/unions/remove-user-from-org-union.js @@ -8,9 +8,9 @@ export const removeUserFromOrgUnion = new GraphQLUnionType({ types: [affiliationError, removeUserFromOrgResultType], resolveType({ _type }) { if (_type === 'regular') { - return removeUserFromOrgResultType + return removeUserFromOrgResultType.name } else { - return affiliationError + return affiliationError.name } }, }) diff --git a/api-js/src/affiliation/unions/transfer-org-ownership-union.js b/api/src/affiliation/unions/transfer-org-ownership-union.js similarity index 87% rename from api-js/src/affiliation/unions/transfer-org-ownership-union.js rename to api/src/affiliation/unions/transfer-org-ownership-union.js index 65c03043ab..bf9bf28094 100644 --- a/api-js/src/affiliation/unions/transfer-org-ownership-union.js +++ b/api/src/affiliation/unions/transfer-org-ownership-union.js @@ -9,9 +9,9 @@ users to transfer ownership of a given organization, and support any errors that types: [affiliationError, transferOrgOwnershipResult], resolveType({ _type }) { if (_type === 'regular') { - return transferOrgOwnershipResult + return transferOrgOwnershipResult.name } else { - return affiliationError + return affiliationError.name } }, }) diff --git a/api-js/src/affiliation/unions/update-user-role-union.js b/api/src/affiliation/unions/update-user-role-union.js similarity index 86% rename from api-js/src/affiliation/unions/update-user-role-union.js rename to api/src/affiliation/unions/update-user-role-union.js index 9356e5d724..9c54aa83cf 100644 --- a/api-js/src/affiliation/unions/update-user-role-union.js +++ b/api/src/affiliation/unions/update-user-role-union.js @@ -8,9 +8,9 @@ export const updateUserRoleUnion = new GraphQLUnionType({ types: [affiliationError, updateUserRoleResultType], resolveType({ _type }) { if (_type === 'regular') { - return updateUserRoleResultType + return updateUserRoleResultType.name } else { - return affiliationError + return affiliationError.name } }, }) diff --git a/api/src/audit-logs/data-source.js b/api/src/audit-logs/data-source.js new file mode 100644 index 0000000000..054ad8f259 --- /dev/null +++ b/api/src/audit-logs/data-source.js @@ -0,0 +1,39 @@ +import { loadAuditLogByKey, loadAuditLogsByOrgId } from './loaders' + +export class AuditLogsDataSource { + constructor({ query, userKey, cleanseInput, i18n, transaction, collections }) { + this._query = query + this._transaction = transaction + this._collections = collections + this.byKey = loadAuditLogByKey({ query, userKey, i18n }) + this.getConnectionsByOrgId = loadAuditLogsByOrgId({ query, userKey, cleanseInput, i18n }) + } + + async logActivity({ initiatedBy, action, target, reason = '' }) { + const auditLog = { + timestamp: new Date().toISOString(), + initiatedBy, + target, + action, + reason, + } + + const trx = await this._transaction(this._collections) + + try { + await trx.step( + () => this._query` + WITH auditLogs + INSERT ${auditLog} INTO auditLogs + RETURN MERGE({ id: NEW._key, _type: "auditLog" }, NEW) + `, + ) + await trx.commit() + } catch (err) { + console.error(`Transaction error occurred while attempting to log user action: ${err}`) + await trx.abort() + } + + return auditLog + } +} diff --git a/api/src/audit-logs/index.js b/api/src/audit-logs/index.js new file mode 100644 index 0000000000..b6bdbe9eea --- /dev/null +++ b/api/src/audit-logs/index.js @@ -0,0 +1,6 @@ +export * from './data-source' +export * from './input' +export * from './loaders' +export * from './mutations' +export * from './objects' +export * from './queries' diff --git a/api/src/audit-logs/input/index.js b/api/src/audit-logs/input/index.js new file mode 100644 index 0000000000..bd4dc77baf --- /dev/null +++ b/api/src/audit-logs/input/index.js @@ -0,0 +1,2 @@ +export * from './log-filters' +export * from './log-order' diff --git a/api/src/audit-logs/input/log-filters.js b/api/src/audit-logs/input/log-filters.js new file mode 100644 index 0000000000..2eed67ee86 --- /dev/null +++ b/api/src/audit-logs/input/log-filters.js @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLList } from 'graphql' +import { ResourceTypeEnums } from '../../enums/resource-type' +import { UserActionEnums } from '../../enums/user-action' + +export const logFilters = new GraphQLInputObjectType({ + name: 'LogFilters', + description: 'Filtering options for audit logs.', + fields: () => ({ + resource: { + type: new GraphQLList(ResourceTypeEnums), + description: 'List of resource types to include when returning logs.', + }, + action: { + type: new GraphQLList(UserActionEnums), + description: 'List of user actions to include when returning logs.', + }, + }), +}) diff --git a/api/src/audit-logs/input/log-order.js b/api/src/audit-logs/input/log-order.js new file mode 100644 index 0000000000..f37bb63ee9 --- /dev/null +++ b/api/src/audit-logs/input/log-order.js @@ -0,0 +1,17 @@ +import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' +import { OrderDirection, LogOrderField } from '../../enums' + +export const logOrder = new GraphQLInputObjectType({ + name: 'LogOrder', + description: 'Ordering options for audit logs.', + fields: () => ({ + field: { + type: new GraphQLNonNull(LogOrderField), + description: 'The field to order logs by.', + }, + direction: { + type: new GraphQLNonNull(OrderDirection), + description: 'The ordering direction.', + }, + }), +}) diff --git a/api/src/audit-logs/loaders/__tests__/load-audit-logs-by-org-id.test.js b/api/src/audit-logs/loaders/__tests__/load-audit-logs-by-org-id.test.js new file mode 100644 index 0000000000..769d966890 --- /dev/null +++ b/api/src/audit-logs/loaders/__tests__/load-audit-logs-by-org-id.test.js @@ -0,0 +1,568 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput } from '../../../validators' +import { loadAuditLogsByOrgId, loadAuditLogByKey } from '../index' +import { toGlobalId } from 'graphql-relay' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the load log connection using org id function', () => { + let query, drop, truncate, collections, user, log1, log2, log3, i18n, language + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + + beforeAll(async () => { + console.error = mockedError + console.warn = mockedWarn + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful load', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + language = 'en' + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }) + log1 = await collections.auditLogs.save({ + timestamp: 'Hello World', + initiatedBy: { + id: '0ca868c6-224c-446d-aaa9-c96f9a091f7e', + userName: 'user1', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'add', + target: { + resource: 'user3', + organization: { + name: 'Hello World', + }, + resourceType: 'user', + updatedProperties: [], + }, + reason: '', + }) + log2 = await collections.auditLogs.save({ + timestamp: 'Hello World', + initiatedBy: { + id: '0ca868c6-224c-446d-aaa9-c96f9a091f7e', + userName: 'user2', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'update', + target: { + resource: 'org1', + organization: { + name: 'Hello World', + }, + resourceType: 'organization', + updatedProperties: [ + { + name: 'Hello World', + oldValue: 'Hello World', + newValue: 'Hello World', + }, + ], + }, + reason: '', + }) + log3 = await collections.auditLogs.save({ + timestamp: 'Hello World', + initiatedBy: { + id: '0ca868c6-224c-446d-aaa9-c96f9a091f7e', + userName: 'user3', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'remove', + target: { + resource: 'domain1', + organization: { + name: 'Hello World', + }, + resourceType: 'domain', + updatedProperties: [], + }, + reason: 'wrong_org', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('using first limit', () => { + it('returns a log', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + language, + userKey: user._key, + cleanseInput, + }) + + const connectionArgs = { + first: 1, + } + const logs = await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + + const logLoader = loadAuditLogByKey({ query }) + const expectedLogs = await logLoader.loadMany([log1._key, log2._key]) + + expectedLogs[0].id = expectedLogs[0]._key + expectedLogs[1].id = expectedLogs[1]._key + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('auditLog', expectedLogs[0]._key), + node: { + ...expectedLogs[0], + }, + }, + ], + pageInfo: { + hasNextPage: true, + hasPreviousPage: false, + startCursor: toGlobalId('auditLog', expectedLogs[0]._key), + endCursor: toGlobalId('auditLog', expectedLogs[0]._key), + }, + totalCount: 3, + } + + expect(logs).toEqual(expectedStructure) + }) + }) + describe('using search argument', () => { + beforeEach(async () => { + // This is used to sync the view before running the test below + await query` + FOR log IN auditLogSearch + SEARCH log.target.resource == "log" + OPTIONS { waitForSync: true } + RETURN log + ` + }) + it('returns filtered logs', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + language, + cleanseInput, + auth: { loginRequired: true }, + }) + + const logLoader = loadAuditLogByKey({ query }) + const expectedLog = await logLoader.load(log3._key) + + const connectionArgs = { + first: 1, + permission: 'super_admin', + search: 'domain1', + } + + const logs = await connectionLoader({ ...connectionArgs }) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('auditLog', expectedLog._key), + node: { + ...expectedLog, + }, + }, + ], + totalCount: 1, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('auditLog', expectedLog._key), + endCursor: toGlobalId('auditLog', expectedLog._key), + }, + } + + expect(logs).toEqual(expectedStructure) + }) + }) + describe('no logs are found', () => { + it('returns an empty structure', async () => { + await truncate() + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + language, + cleanseInput, + auth: { loginRequired: true }, + }) + + const connectionArgs = { + first: 10, + } + const logs = await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + + const expectedStructure = { + edges: [], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + totalCount: 0, + } + + expect(logs).toEqual(expectedStructure) + }) + }) + describe('using orderByField', () => { + describe('using after cursor', () => { + describe('ordering on TIMESTAMP', () => { + describe('order direction is ASC', () => { + it('returns logs in order', async () => { + const logLoader = loadAuditLogByKey({ query }) + const expectedLogs = await logLoader.loadMany([log1._key, log2._key, log3._key]) + + expectedLogs[0].id = expectedLogs[0]._key + expectedLogs[1].id = expectedLogs[1]._key + expectedLogs[2].id = expectedLogs[2]._key + + const connectionArgs = { + first: 1, + after: toGlobalId('auditLog', expectedLogs[1]._key), + orderBy: { + field: 'timestamp', + direction: 'ASC', + }, + } + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + language, + cleanseInput, + auth: { loginRequired: true }, + }) + const logs = await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('auditLog', expectedLogs[2]._key), + node: { + ...expectedLogs[2], + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: false, + hasPreviousPage: true, + startCursor: toGlobalId('auditLog', expectedLogs[2]._key), + endCursor: toGlobalId('auditLog', expectedLogs[2]._key), + }, + } + + expect(logs).toEqual(expectedStructure) + }) + }) + }) + }) + }) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given an unsuccessful load', () => { + describe('limits are not set', () => { + it('returns an error message', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = {} + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual( + new Error(`You must provide a \`first\` or \`last\` value to properly paginate the \`Log\` connection.`), + ) + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadAuditLogsByOrgId.`, + ]) + }) + }) + describe('both limits are set', () => { + it('returns an error message', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + first: 1, + last: 1, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual( + new Error(`Passing both \`first\` and \`last\` to paginate the \`Log\` connection is not supported.`), + ) + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadAuditLogsByOrgId.`, + ]) + }) + }) + describe('limits are set below minimum', () => { + describe('first limit is set', () => { + it('returns an error message', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + first: -5, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual(new Error(`\`first\` on the \`Log\` connection cannot be less than zero.`)) + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to have \`first\` set below zero for: loadAuditLogsByOrgId.`, + ]) + }) + }) + describe('last limit is set', () => { + it('returns an error message', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + last: -5, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual(new Error(`\`last\` on the \`Log\` connection cannot be less than zero.`)) + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to have \`last\` set below zero for: loadAuditLogsByOrgId.`, + ]) + }) + }) + }) + describe('limits are set above maximum', () => { + describe('first limit is set', () => { + it('returns an error message', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + first: 1000, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual( + new Error( + `Requesting \`1000\` records on the \`Log\` connection exceeds the \`first\` limit of 100 records.`, + ), + ) + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to have \`first\` set to 1000 for: loadAuditLogsByOrgId.`, + ]) + }) + }) + describe('last limit is set', () => { + it('returns an error message', async () => { + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + last: 1000, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual( + new Error( + `Requesting \`1000\` records on the \`Log\` connection exceeds the \`last\` limit of 100 records.`, + ), + ) + } + + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to have \`last\` set to 1000 for: loadAuditLogsByOrgId.`, + ]) + }) + }) + }) + }) + describe('given a database error', () => { + describe('when gathering log keys that are claimed by orgs that the user has affiliations to', () => { + it('returns an error message', async () => { + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) + + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + first: 5, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual(new Error('Unable to query log(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Database error occurred while user: ${user._key} was trying to query logs in loadAuditLogsByOrgId, error: Error: Database Error Occurred.`, + ]) + }) + }) + }) + describe('given a cursor error', () => { + describe('when gathering log keys that are claimed by orgs that the user has affiliations to', () => { + it('returns an error message', async () => { + const cursor = { + next() { + throw new Error('Cursor error occurred.') + }, + } + const query = jest.fn().mockReturnValueOnce(cursor) + + const connectionLoader = loadAuditLogsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + }) + + const connectionArgs = { + first: 5, + } + try { + await connectionLoader({ + permission: 'super_admin', + ...connectionArgs, + }) + } catch (err) { + expect(err).toEqual(new Error('Unable to load log(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Cursor error occurred while user: ${user._key} was trying to gather logs in loadAuditLogsByOrgId, error: Error: Cursor error occurred.`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/audit-logs/loaders/index.js b/api/src/audit-logs/loaders/index.js new file mode 100644 index 0000000000..e4196d2db8 --- /dev/null +++ b/api/src/audit-logs/loaders/index.js @@ -0,0 +1,2 @@ +export * from './load-audit-log-by-key' +export * from './load-audit-logs-by-org-id' diff --git a/api/src/audit-logs/loaders/load-audit-log-by-key.js b/api/src/audit-logs/loaders/load-audit-log-by-key.js new file mode 100644 index 0000000000..5b8cc0be6c --- /dev/null +++ b/api/src/audit-logs/loaders/load-audit-log-by-key.js @@ -0,0 +1,35 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadAuditLogByKey = ({ query, userKey, i18n }) => + new DataLoader(async (ids) => { + let cursor + + try { + cursor = await query` + WITH auditLogs + FOR log IN auditLogs + FILTER log._key IN ${ids} + RETURN MERGE({ id: log._key, _type: "auditLog" }, log) + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} running loadAuditLogByKey: ${err}`, + ) + throw new Error(i18n._(t`Unable to load log. Please try again.`)) + } + + const logMap = {} + try { + await cursor.forEach((log) => { + logMap[log._key] = log + }) + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} running loadAuditLogByKey: ${err}`, + ) + throw new Error(i18n._(t`Unable to load log. Please try again.`)) + } + + return ids.map((id) => logMap[id]) + }) diff --git a/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js b/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js new file mode 100644 index 0000000000..065157677f --- /dev/null +++ b/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js @@ -0,0 +1,351 @@ +import { aql } from 'arangojs' +import { fromGlobalId, toGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +export const loadAuditLogsByOrgId = + ({ query, userKey, i18n, cleanseInput }) => + async ({ + orgId, + permission, + after, + before, + first, + last, + orderBy, + search, + filters = { resource: [], action: [] }, + }) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(log._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(auditLogs, ${afterId})` + + let documentField = aql`` + let logField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + documentField = aql`afterVar.timestamp` + logField = aql`log.timestamp` + } else if (orderBy.field === 'initiated_by') { + documentField = aql`afterVar.initiatedBy.userName` + logField = aql`log.initiatedBy.userName` + } else if (orderBy.field === 'resource_name') { + documentField = aql`afterVar.target.resource` + logField = aql`log.target.resource` + } + + afterTemplate = aql` + FILTER ${logField} ${afterTemplateDirection} ${documentField} + OR (${logField} == ${documentField} + AND TO_NUMBER(log._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(log._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(auditLogs, ${beforeId})` + + let documentField = aql`` + let logField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + documentField = aql`beforeVar.timestamp` + logField = aql`log.timestamp` + } else if (orderBy.field === 'initiated_by') { + documentField = aql`beforeVar.initiatedBy.userName` + logField = aql`log.initiatedBy.userName` + } else if (orderBy.field === 'resource_name') { + documentField = aql`beforeVar.target.resource` + logField = aql`log.target.resource` + } + + beforeTemplate = aql` + FILTER ${logField} ${beforeTemplateDirection} ${documentField} + OR (${logField} == ${documentField} + AND TO_NUMBER(log._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAuditLogsByOrgId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Log\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn(`User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAuditLogsByOrgId.`) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Log\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAuditLogsByOrgId.`) + throw new Error(i18n._(t`\`${argSet}\` on the \`Log\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAuditLogsByOrgId.`) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Log\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(log._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(log._key) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAuditLogsByOrgId.`) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(log._key) > TO_NUMBER(LAST(retrievedLogs)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(log._key) < TO_NUMBER(FIRST(retrievedLogs)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let logField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + logField = aql`log.timestamp` + hasNextPageDocumentField = aql`LAST(retrievedLogs).timestamp` + hasPreviousPageDocumentField = aql`FIRST(retrievedLogs).timestamp` + } else if (orderBy.field === 'initiated_by') { + logField = aql`log.initiatedBy.userName` + hasNextPageDocumentField = aql`LAST(retrievedLogs).initiatedBy.userName` + hasPreviousPageDocumentField = aql`FIRST(retrievedLogs).initiatedBy.userName` + } else if (orderBy.field === 'resource_name') { + logField = aql`log.target.resource` + hasNextPageDocumentField = aql`LAST(retrievedLogs).target.resource` + hasPreviousPageDocumentField = aql`FIRST(retrievedLogs).target.resource` + } + hasNextPageFilter = aql` + FILTER ${logField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${logField} == ${hasNextPageDocumentField} + AND TO_NUMBER(log._key) > TO_NUMBER(LAST(retrievedLogs)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${logField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${logField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(log._key) < TO_NUMBER(FIRST(retrievedLogs)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + sortByField = aql`log.timestamp ${orderBy.direction},` + } else if (orderBy.field === 'initiated_by') { + sortByField = aql`log.initiatedBy.userName ${orderBy.direction},` + } else if (orderBy.field === 'resource_name') { + sortByField = aql`log.target.resource ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let resourceFilters = aql`` + if (filters.resource.length > 0) { + resourceFilters = aql`FILTER log.target.resourceType IN ${filters.resource}` + } + + let actionFilters = aql`` + if (filters.action.length > 0) { + actionFilters = aql`FILTER log.action IN ${filters.action}` + } + + let logKeysQuery + if (typeof orgId !== 'undefined' && orgId !== null) { + logKeysQuery = aql` + WITH auditLogs + LET logKeys = ( + FOR log IN auditLogs + FILTER log.target.organization.id == ${orgId} + RETURN log._key + ) + ` + } else if (permission === 'super_admin') { + logKeysQuery = aql` + WITH auditLogs + LET logKeys = ( + FOR log IN auditLogs + RETURN log._key + ) + ` + } else { + throw new Error(i18n._(t`Cannot query audit logs on organization without admin permission or higher.`)) + } + + let logQuery = aql`` + let loopString = aql`FOR log IN auditLogs` + let totalCount = aql`LENGTH(logKeys)` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + logQuery = aql` + LET tokenArr = TOKENS(${search}, "text_en") + LET searchedLogs = ( + FOR tokenItem in tokenArr + LET token = LOWER(tokenItem) + FOR log IN auditLogSearch + SEARCH ANALYZER( + log.initiatedBy.userName LIKE CONCAT("%", token, "%") + OR log.target.resource LIKE CONCAT("%", token, "%") + , "text_en") + FILTER log._key IN logKeys + RETURN log + ) + ` + loopString = aql`FOR log IN searchedLogs` + totalCount = aql`LENGTH(searchedLogs)` + } + + let requestedLogInfo + try { + requestedLogInfo = await query` + ${logKeysQuery} + ${logQuery} + + ${afterVar} + ${beforeVar} + + LET retrievedLogs = ( + ${loopString} + FILTER log._key IN logKeys + ${resourceFilters} + ${actionFilters} + + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE({ id: log._key, _type: "auditLog" }, log) + ) + + LET hasNextPage = (LENGTH( + ${loopString} + FILTER log._key IN logKeys + ${resourceFilters} + ${actionFilters} + + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(log._key) ${sortString} LIMIT 1 + RETURN log + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + ${loopString} + FILTER log._key IN logKeys + ${resourceFilters} + ${actionFilters} + + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(log._key) ${sortString} LIMIT 1 + RETURN log + ) > 0 ? true : false) + + RETURN { + "auditLogs": retrievedLogs, + "totalCount": ${totalCount}, + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedLogs)._key, + "endKey": LAST(retrievedLogs)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query logs in loadAuditLogsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to query log(s). Please try again.`)) + } + + let logsInfo + try { + logsInfo = await requestedLogInfo.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather logs in loadAuditLogsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load log(s). Please try again.`)) + } + + if (logsInfo.auditLogs.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = logsInfo.auditLogs.map((log) => { + return { + cursor: toGlobalId('auditLog', log._key), + node: log, + } + }) + + return { + edges, + totalCount: logsInfo.totalCount, + pageInfo: { + hasNextPage: logsInfo.hasNextPage, + hasPreviousPage: logsInfo.hasPreviousPage, + startCursor: toGlobalId('auditLog', logsInfo.startKey), + endCursor: toGlobalId('auditLog', logsInfo.endKey), + }, + } + } diff --git a/api/src/audit-logs/mutations/index.js b/api/src/audit-logs/mutations/index.js new file mode 100644 index 0000000000..b25f522ac0 --- /dev/null +++ b/api/src/audit-logs/mutations/index.js @@ -0,0 +1 @@ +export * from './log-activity' diff --git a/api/src/audit-logs/mutations/log-activity.js b/api/src/audit-logs/mutations/log-activity.js new file mode 100644 index 0000000000..46673cb5e3 --- /dev/null +++ b/api/src/audit-logs/mutations/log-activity.js @@ -0,0 +1,58 @@ +export const logActivity = async ({ + transaction, + collections, + query, + initiatedBy = { + id: '', + userName: '', + ipAddress: '', // IP address of user + role: '', // permission level of user + organization: '', // org affiliation of user + }, + action = '', + target = { + resource: '', // name of resource being acted upon + organization: '', // affiliated org (optional) + resourceType: '', // user, org, domain + updatedProperties: [], + }, + reason = '', +}) => { + const auditLog = { + timestamp: new Date().toISOString(), + initiatedBy, + target, + action, + reason, + } + + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH auditLogs + INSERT ${auditLog} INTO auditLogs + RETURN MERGE( + { + id: NEW._key, + _type: "auditLog" + }, + NEW + ) + `, + ) + } catch (err) { + console.error(`Transaction step error occurred while attempting to log user action: ${err}`) + await trx.abort() + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while attempting to log user action: ${err}`) + await trx.abort() + } + + return auditLog +} diff --git a/api/src/audit-logs/objects/audit-log.js b/api/src/audit-logs/objects/audit-log.js new file mode 100644 index 0000000000..87e905a844 --- /dev/null +++ b/api/src/audit-logs/objects/audit-log.js @@ -0,0 +1,43 @@ +import { GraphQLObjectType } from 'graphql' +import { globalIdField } from 'graphql-relay' +import { DomainRemovalReasonEnum } from '../../enums' +import { UserActionEnums } from '../../enums/user-action' +import { nodeInterface } from '../../node' +import { initiatedByType } from './initiated-by' +import { targetResourceType } from './target-resource' +import { GraphQLDateTime } from 'graphql-scalars' + +export const auditLogType = new GraphQLObjectType({ + name: 'AuditLog', + description: + 'A record of activity that modified the state of a user, domain, or organization', + fields: () => ({ + id: globalIdField('auditLog'), + timestamp: { + type: GraphQLDateTime, + description: 'Datetime string the activity occurred.', + resolve: ({ timestamp }) => timestamp, + }, + initiatedBy: { + type: initiatedByType, + description: 'Username of admin that initiated the activity.', + resolve: ({ initiatedBy }) => initiatedBy, + }, + action: { + type: UserActionEnums, + description: 'Type of activity that was initiated.', + resolve: ({ action }) => action, + }, + target: { + type: targetResourceType, + description: 'Information on targeted resource.', + resolve: ({ target }) => target, + }, + reason: { + type: DomainRemovalReasonEnum, + description: 'Optional reason for action, used for domain removal.', + resolve: ({ reason }) => reason, + }, + }), + interfaces: [nodeInterface], +}) diff --git a/api/src/audit-logs/objects/index.js b/api/src/audit-logs/objects/index.js new file mode 100644 index 0000000000..53331a1812 --- /dev/null +++ b/api/src/audit-logs/objects/index.js @@ -0,0 +1,4 @@ +export * from './audit-log' +export * from './initiated-by' +export * from './log-connection' +export * from './target-resource' diff --git a/api/src/audit-logs/objects/initiated-by.js b/api/src/audit-logs/objects/initiated-by.js new file mode 100644 index 0000000000..9a1347e434 --- /dev/null +++ b/api/src/audit-logs/objects/initiated-by.js @@ -0,0 +1,37 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { globalIdField } from 'graphql-relay' +import { GraphQLEmailAddress, GraphQLIP } from 'graphql-scalars' +import { RoleEnums } from '../../enums' + +export const initiatedByType = new GraphQLObjectType({ + name: 'InitiatedBy', + description: 'Information on the user that initiated the logged action', + fields: () => ({ + id: globalIdField('user'), + userName: { + type: GraphQLEmailAddress, + description: 'User email address.', + resolve: ({ userName }) => userName, + }, + ipAddress: { + type: GraphQLIP, + description: 'User IP address.', + resolve: async ({ ipAddress }, _args, { auth: { checkSuperAdmin, superAdminRequired } }) => { + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ isSuperAdmin }) + + return ipAddress + }, + }, + role: { + type: RoleEnums, + description: 'User permission level.', + resolve: ({ role }) => role, + }, + organization: { + type: GraphQLString, + description: 'User affiliated organization.', + resolve: ({ organization }) => organization, + }, + }), +}) diff --git a/api/src/audit-logs/objects/log-connection.js b/api/src/audit-logs/objects/log-connection.js new file mode 100644 index 0000000000..f5527f47f8 --- /dev/null +++ b/api/src/audit-logs/objects/log-connection.js @@ -0,0 +1,16 @@ +import { GraphQLInt } from 'graphql' +import { connectionDefinitions } from 'graphql-relay' + +import { auditLogType } from '../objects/audit-log' + +export const logConnection = connectionDefinitions({ + name: 'AuditLog', + nodeType: auditLogType, + connectionFields: () => ({ + totalCount: { + type: GraphQLInt, + description: 'The total amount of logs the user has access to.', + resolve: ({ totalCount }) => totalCount, + }, + }), +}) diff --git a/api/src/audit-logs/objects/target-resource.js b/api/src/audit-logs/objects/target-resource.js new file mode 100644 index 0000000000..e89b43b328 --- /dev/null +++ b/api/src/audit-logs/objects/target-resource.js @@ -0,0 +1,84 @@ +import { GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql' +import { globalIdField } from 'graphql-relay' +import { ResourceTypeEnums } from '../../enums/resource-type' + +export const targetResourceType = new GraphQLObjectType({ + name: 'TargetResource', + description: 'Resource that was the target of a specified action by a user.', + fields: () => ({ + resource: { + type: GraphQLString, + description: 'Name of the targeted resource.', + resolve: async ({ resource, resourceType }, _, { language }) => { + if (resourceType === 'organization') { + return resource[`${language}`] + } + return resource + }, + }, + organization: { + type: new GraphQLObjectType({ + name: 'TargetOrganization', + description: 'Organization that the resource is affiliated with.', + fields: () => ({ + id: globalIdField('organization'), + name: { + type: GraphQLString, + description: 'Name of the affiliated organization.', + resolve: async ({ id, name }, _, { loaders: { loadOrgByKey } }) => { + const org = await loadOrgByKey.load(id) + if (typeof org === 'undefined') { + return name + } + return org.name + }, + }, + }), + }), + description: 'Organization that the resource is affiliated with.', + resolve: ({ organization }) => organization, + }, + resourceType: { + type: ResourceTypeEnums, + description: 'Type of resource that was modified: user, domain, or organization.', + resolve: ({ resourceType }) => resourceType, + }, + updatedProperties: { + type: new GraphQLList( + new GraphQLObjectType({ + name: 'UpdatedProperties', + description: 'Object describing how a resource property was updated.', + fields: () => ({ + name: { + type: GraphQLString, + description: 'Name of updated resource.', + resolve: ({ name }) => name, + }, + oldValue: { + type: GraphQLString, + description: 'Old value of updated property.', + resolve: ({ name, oldValue }) => { + if (name === 'selectors' || name === 'tags') { + return JSON.stringify(oldValue) + } + return oldValue + }, + }, + newValue: { + type: GraphQLString, + description: 'New value of updated property.', + resolve: ({ name, newValue }) => { + if (name === 'selectors' || name === 'tags') { + return JSON.stringify(newValue) + } + return newValue + }, + }, + }), + }), + ), + description: 'List of resource properties that were modified.', + resolve: ({ updatedProperties }) => updatedProperties, + }, + }), +}) diff --git a/api/src/audit-logs/queries/find-audit-logs.js b/api/src/audit-logs/queries/find-audit-logs.js new file mode 100644 index 0000000000..b10186c116 --- /dev/null +++ b/api/src/audit-logs/queries/find-audit-logs.js @@ -0,0 +1,63 @@ +import { connectionArgs, fromGlobalId } from 'graphql-relay' +import { GraphQLID, GraphQLString } from 'graphql' +import { logConnection } from '../objects/log-connection' +import { logOrder } from '../input/log-order' +import { t } from '@lingui/macro' +import { logFilters } from '../input/log-filters' +import ac from '../../access-control' + +export const findAuditLogs = { + type: logConnection.connectionType, + description: 'Select activity logs a user has access to.', + args: { + orgId: { + type: GraphQLID, + description: 'The organization you wish to query the logs from.', + }, + orderBy: { + type: logOrder, + description: 'Ordering options for log connections.', + }, + search: { + type: GraphQLString, + description: 'String used to search for logs by initiant user or target resource.', + }, + filters: { + type: logFilters, + description: 'Keywords used to filter log results.', + }, + ...connectionArgs, + }, + resolve: async ( + _, + args, + { + userKey, + i18n, + auth: { checkPermission, userRequired, verifiedRequired }, + loaders: { loadOrgByKey }, + dataSources: { auditLogs }, + validators: { cleanseInput }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + // Get Org from db + const org = await loadOrgByKey.load(orgId) + + // Check to see if user belongs to org + const permission = await checkPermission({ orgId: org?._id }) + if (!ac.can(permission).readOwn('log').granted) { + throw new Error(i18n._(t`Cannot query audit logs on organization without admin permission or higher.`)) + } + const auditLogCollection = await auditLogs.getConnectionsByOrgId({ + ...args, + orgId: org?._key, + permission, + }) + console.info(`User: ${userKey} successfully retrieved audit logs.`) + return auditLogCollection + }, +} diff --git a/api/src/audit-logs/queries/index.js b/api/src/audit-logs/queries/index.js new file mode 100644 index 0000000000..03baafc7e1 --- /dev/null +++ b/api/src/audit-logs/queries/index.js @@ -0,0 +1 @@ +export * from './find-audit-logs' diff --git a/api/src/auth/checks/__tests__/check-domain-ownership.test.js b/api/src/auth/checks/__tests__/check-domain-ownership.test.js new file mode 100644 index 0000000000..d66f96673a --- /dev/null +++ b/api/src/auth/checks/__tests__/check-domain-ownership.test.js @@ -0,0 +1,569 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import { checkDomainOwnership } from '../index' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the check domain ownership function', () => { + let query, drop, truncate, collections, org, verifiedOrg, unverifiedOrg, domain, i18n, user + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + verified: true, + }) + verifiedOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'other-org', + acronym: 'OO', + name: 'Other Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'autre-org', + acronym: 'AO', + name: 'Autre Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + verified: true, + }) + unverifiedOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'unverified-org', + acronym: 'UO', + name: 'Unverified Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'unverified-org', + acronym: 'UO', + name: 'Unverified Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + lastRan: null, + selectors: ['selector1', 'selector2'], + }) + await collections.ownership.save({ + _to: domain._id, + _from: org._id, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('given a successful domain ownership call', () => { + let permitted + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + + describe('if the user belongs to an org which is verified and the org in question is also verified', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: verifiedOrg._id, + _to: user._id, + permission: 'user', + }) + }) + it('will return true', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + query, + userKey: user._key, + }) + permitted = await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + expect(permitted).toEqual(true) + }) + }) + + describe('if the user belongs to an org which has a ownership for a given organization', () => { + afterEach(async () => { + await query` + LET userEdges = (FOR v, e IN 1..1 ANY ${org._id} affiliations RETURN { edgeKey: e._key, userKey: e._to }) + LET removeUserEdges = (FOR userEdge IN userEdges REMOVE userEdge.edgeKey IN affiliations) + RETURN true + ` + await query` + FOR affiliation IN affiliations + REMOVE affiliation IN affiliations + ` + }) + describe('if the user has super-admin-level permissions', () => { + describe('domain has dmarc reports', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it('will return true', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + query, + userKey: user._key, + auth: { loginRequiredBool: true }, + }) + permitted = await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + expect(permitted).toEqual(true) + }) + }) + }) + describe('if the user has admin-level permissions', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + }) + it('will return true', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + query, + userKey: user._key, + auth: { loginRequiredBool: true }, + }) + permitted = await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + expect(permitted).toEqual(true) + }) + }) + describe('if the user has user-level permissions', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('will return true', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + query, + userKey: user._key, + auth: { loginRequiredBool: true }, + }) + permitted = await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + expect(permitted).toEqual(true) + }) + }) + }) + }) + describe('given an unsuccessful domain ownership call', () => { + describe('user is a super admin, but domain does not have any dmarc reports', () => { + describe('domain does not have dmarc reports', () => { + it('will return false', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValue({ superAdmin: true, domainOwnership: false }), + }), + userKey: 123, + auth: { loginRequiredBool: true }, + }) + const permitted = await testCheckDomainOwnerShip({ + domainId: 'domains/123', + }) + expect(permitted).toEqual(false) + }) + }) + }) + describe('if the user does not belong to an org which has a ownership for a given domain', () => { + let permitted + beforeEach(async () => { + await collections.affiliations.save({ + _from: unverifiedOrg._id, + _to: user._id, + permission: 'user', + }) + }) + it('will return false', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n: i18n, + query: query, + userKey: '123', + }) + permitted = await testCheckDomainOwnerShip({ + domainId: 'domains/123', + }) + expect(permitted).toEqual(false) + }) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('if a cursor error is encountered during super admin ownership check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + const firstCursor = { + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), + } + mockQuery = jest.fn().mockReturnValueOnce(firstCursor) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) + expect(consoleOutput).toEqual([ + `Cursor error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, + ]) + } + }) + }) + describe('if a cursor error is encountered during ownership check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + const firstCursor = { + next: jest.fn().mockReturnValue({ + superAdmin: false, + domainOwnership: false, + }), + } + const secondCursor = { + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), + } + mockQuery = jest.fn().mockReturnValueOnce(firstCursor).mockReturnValue(secondCursor) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) + expect(consoleOutput).toEqual([ + `Cursor error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, + ]) + } + }) + }) + describe('if a database error is encountered during super admin check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) + expect(consoleOutput).toEqual([ + `Database error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, + ]) + } + }) + }) + describe('if a database error is encountered during ownership check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + mockQuery = jest + .fn() + .mockReturnValueOnce({ + next() { + return { + superAdmin: false, + domainOwnership: false, + } + }, + }) + .mockRejectedValue(new Error('Database error occurred.')) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) + expect(consoleOutput).toEqual([ + `Database error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, + ]) + } + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('if a cursor error is encountered during super admin ownership check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + const firstCursor = { + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), + } + mockQuery = jest.fn().mockReturnValueOnce(firstCursor) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual( + new Error( + 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', + ), + ) + expect(consoleOutput).toEqual([ + `Cursor error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, + ]) + } + }) + }) + describe('if a cursor error is encountered during ownership check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + const firstCursor = { + next: jest.fn().mockReturnValue({ + superAdmin: false, + domainOwnership: false, + }), + } + const secondCursor = { + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), + } + mockQuery = jest.fn().mockReturnValueOnce(firstCursor).mockReturnValue(secondCursor) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual( + new Error( + 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', + ), + ) + expect(consoleOutput).toEqual([ + `Cursor error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, + ]) + } + }) + }) + describe('if a database error is encountered during super admin check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual( + new Error( + 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', + ), + ) + expect(consoleOutput).toEqual([ + `Database error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, + ]) + } + }) + }) + describe('if a database error is encountered during ownership check', () => { + let mockQuery + it('returns an appropriate error message', async () => { + mockQuery = jest + .fn() + .mockReturnValueOnce({ + next() { + return { + superAdmin: false, + domainOwnership: false, + } + }, + }) + .mockRejectedValue(new Error('Database error occurred.')) + try { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + auth: { loginRequired: true }, + query: mockQuery, + userKey: user._key, + }) + await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + } catch (err) { + expect(err).toEqual( + new Error( + 'Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.', + ), + ) + expect(consoleOutput).toEqual([ + `Database error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, + ]) + } + }) + }) + }) + }) +}) diff --git a/api-js/src/auth/__tests__/check-domain-permission.test.js b/api/src/auth/checks/__tests__/check-domain-permission.test.js similarity index 88% rename from api-js/src/auth/__tests__/check-domain-permission.test.js rename to api/src/auth/checks/__tests__/check-domain-permission.test.js index 6ad5afb859..0682857b02 100644 --- a/api-js/src/auth/__tests__/check-domain-permission.test.js +++ b/api/src/auth/checks/__tests__/check-domain-permission.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' import { checkDomainPermission } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -24,18 +25,21 @@ describe('given the check domain permission function', () => { let user, permitted beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -149,7 +153,7 @@ describe('given the check domain permission function', () => { query: jest .fn() .mockReturnValueOnce({ count: 0 }) - .mockReturnValue({ next: jest.fn().mockReturnValue([]) }), + .mockReturnValue({ next: jest.fn().mockReturnValue(false) }), userKey: 123, }) permitted = await testCheckDomainPermission({ domainId: 'domains/123' }) @@ -174,9 +178,7 @@ describe('given the check domain permission function', () => { describe('if a database error is encountered during super admin permission check', () => { let mockQuery it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainPermission = checkDomainPermission({ i18n, @@ -185,11 +187,7 @@ describe('given the check domain permission function', () => { }) await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { - expect(err).toEqual( - new Error( - 'Permission check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Permission check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Database error when retrieving super admin claims for user: 123 and domain: domains/123: Error: Database error occurred.`, ]) @@ -211,11 +209,7 @@ describe('given the check domain permission function', () => { }) await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { - expect(err).toEqual( - new Error( - 'Permission check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Permission check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Database error when retrieving affiliated organization claims for user: 123 and domain: domains/123: Error: Database error occurred.`, ]) @@ -230,10 +224,7 @@ describe('given the check domain permission function', () => { throw new Error('Cursor error occurred.') }, } - mockQuery = jest - .fn() - .mockReturnValueOnce({ count: 0 }) - .mockReturnValue(cursor) + mockQuery = jest.fn().mockReturnValueOnce({ count: 0 }).mockReturnValue(cursor) try { const testCheckDomainPermission = checkDomainPermission({ i18n, @@ -242,11 +233,7 @@ describe('given the check domain permission function', () => { }) await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { - expect(err).toEqual( - new Error( - 'Permission check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Permission check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Cursor error when retrieving affiliated organization claims for user: 123 and domain: domains/123: Error: Cursor error occurred.`, ]) @@ -272,9 +259,7 @@ describe('given the check domain permission function', () => { describe('if a database error is encountered during super admin permission check', () => { let mockQuery it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainPermission = checkDomainPermission({ i18n, @@ -328,10 +313,7 @@ describe('given the check domain permission function', () => { throw new Error('Cursor error occurred.') }, } - mockQuery = jest - .fn() - .mockReturnValueOnce({ count: 1 }) - .mockReturnValue(cursor) + mockQuery = jest.fn().mockReturnValueOnce({ count: 1 }).mockReturnValue(cursor) try { const testCheckDomainPermission = checkDomainPermission({ i18n, diff --git a/api-js/src/auth/__tests__/check-org-owner.test.js b/api/src/auth/checks/__tests__/check-org-owner.test.js similarity index 79% rename from api-js/src/auth/__tests__/check-org-owner.test.js rename to api/src/auth/checks/__tests__/check-org-owner.test.js index 15f4598647..e2a97b2e87 100644 --- a/api-js/src/auth/__tests__/check-org-owner.test.js +++ b/api/src/auth/checks/__tests__/check-org-owner.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' import { checkOrgOwner } from '../check-org-owner' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -13,18 +14,21 @@ describe('given the checkOrgOwner function', () => { let query, drop, truncate, collections, user, org beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -64,8 +68,7 @@ describe('given the checkOrgOwner function', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) }) it('returns true', async () => { @@ -82,7 +85,6 @@ describe('given the checkOrgOwner function', () => { _from: org._id, _to: user._id, permission: 'admin', - owner: false, }) }) it('returns false', async () => { @@ -119,9 +121,7 @@ describe('given the checkOrgOwner function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckOrgOwner = checkOrgOwner({ i18n, @@ -132,9 +132,7 @@ describe('given the checkOrgOwner function', () => { try { await testCheckOrgOwner({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error(`Unable to load owner information. Please try again.`), - ) + expect(err).toEqual(new Error(`Unable to load owner information. Please try again.`)) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: 123 is the owner of: 123: Error: Database error occurred`, @@ -144,9 +142,7 @@ describe('given the checkOrgOwner function', () => { describe('cursor error occurs', () => { it('throws an error', async () => { const mockedQuery = jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred')), }) const testCheckOrgOwner = checkOrgOwner({ @@ -158,9 +154,7 @@ describe('given the checkOrgOwner function', () => { try { await testCheckOrgOwner({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error(`Unable to load owner information. Please try again.`), - ) + expect(err).toEqual(new Error(`Unable to load owner information. Please try again.`)) } expect(consoleOutput).toEqual([ `Cursor error when checking to see if user: 123 is the owner of: 123: Error: Cursor error occurred`, @@ -183,9 +177,7 @@ describe('given the checkOrgOwner function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckOrgOwner = checkOrgOwner({ i18n, @@ -197,9 +189,7 @@ describe('given the checkOrgOwner function', () => { await testCheckOrgOwner({ orgId: '123' }) } catch (err) { expect(err).toEqual( - new Error( - `Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`, - ), + new Error(`Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`), ) } expect(consoleOutput).toEqual([ @@ -210,9 +200,7 @@ describe('given the checkOrgOwner function', () => { describe('cursor error occurs', () => { it('throws an error', async () => { const mockedQuery = jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred')), }) const testCheckOrgOwner = checkOrgOwner({ @@ -225,9 +213,7 @@ describe('given the checkOrgOwner function', () => { await testCheckOrgOwner({ orgId: '123' }) } catch (err) { expect(err).toEqual( - new Error( - `Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`, - ), + new Error(`Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`), ) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/auth/__tests__/check-permission.test.js b/api/src/auth/checks/__tests__/check-permission.test.js similarity index 87% rename from api-js/src/auth/__tests__/check-permission.test.js rename to api/src/auth/checks/__tests__/check-permission.test.js index 655b4e81a5..73171b4092 100644 --- a/api-js/src/auth/__tests__/check-permission.test.js +++ b/api/src/auth/checks/__tests__/check-permission.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' import { checkPermission } from '..' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,18 +24,21 @@ describe('given the check permission function', () => { describe('given a successful permission call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -190,7 +194,7 @@ describe('given the check permission function', () => { query, }) const permission = await testCheckPermission({ orgId: org._id }) - expect(permission).toEqual(undefined) + expect(permission).toEqual(null) }) }) }) @@ -213,9 +217,7 @@ describe('given the check permission function', () => { describe('database error occurs', () => { describe('when checking if super admin', () => { it('throws an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckPermission = checkPermission({ @@ -225,9 +227,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } expect(consoleOutput).toEqual([ @@ -254,9 +254,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } expect(consoleOutput).toEqual([ @@ -283,9 +281,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Unable to check permission. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to check permission. Please try again.')) } expect(consoleOutput).toEqual([ @@ -317,9 +313,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Unable to check permission. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to check permission. Please try again.')) } expect(consoleOutput).toEqual([ @@ -347,9 +341,7 @@ describe('given the check permission function', () => { describe('database error occurs', () => { describe('when checking if super admin', () => { it('throws an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckPermission = checkPermission({ @@ -359,11 +351,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Erreur d'authentification. Veuillez vous connecter.", - ), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } expect(consoleOutput).toEqual([ @@ -390,11 +378,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Erreur d'authentification. Veuillez vous connecter.", - ), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } expect(consoleOutput).toEqual([ @@ -421,11 +405,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de vérifier l'autorisation. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -457,11 +437,7 @@ describe('given the check permission function', () => { }) await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de vérifier l'autorisation. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/auth/__tests__/check-super-admin.test.js b/api/src/auth/checks/__tests__/check-super-admin.test.js similarity index 84% rename from api-js/src/auth/__tests__/check-super-admin.test.js rename to api/src/auth/checks/__tests__/check-super-admin.test.js index 5b05fc905a..f355b27bce 100644 --- a/api-js/src/auth/__tests__/check-super-admin.test.js +++ b/api/src/auth/checks/__tests__/check-super-admin.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' import { checkSuperAdmin } from '../check-super-admin' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,18 +24,21 @@ describe('given the check super admin function', () => { describe('given a successful call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -163,9 +167,7 @@ describe('given the check super admin function', () => { }) describe('given a database error', () => { it('raises an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testSuperAdmin = checkSuperAdmin({ @@ -175,9 +177,7 @@ describe('given the check super admin function', () => { }) await testSuperAdmin() } catch (err) { - expect(err).toEqual( - new Error('Unable to check permission. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to check permission. Please try again.')) } expect(consoleOutput).toEqual([ @@ -202,9 +202,7 @@ describe('given the check super admin function', () => { }) await testSuperAdmin() } catch (err) { - expect(err).toEqual( - new Error('Unable to check permission. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to check permission. Please try again.')) } expect(consoleOutput).toEqual([ @@ -230,9 +228,7 @@ describe('given the check super admin function', () => { }) describe('given a database error', () => { it('raises an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testSuperAdmin = checkSuperAdmin({ @@ -242,11 +238,7 @@ describe('given the check super admin function', () => { }) await testSuperAdmin() } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de vérifier l'autorisation. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -271,11 +263,7 @@ describe('given the check super admin function', () => { }) await testSuperAdmin() } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de vérifier l'autorisation. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/auth/__tests__/check-user-belongs-to-org.test.js b/api/src/auth/checks/__tests__/check-user-belongs-to-org.test.js similarity index 80% rename from api-js/src/auth/__tests__/check-user-belongs-to-org.test.js rename to api/src/auth/checks/__tests__/check-user-belongs-to-org.test.js index 744767127d..6956696979 100644 --- a/api-js/src/auth/__tests__/check-user-belongs-to-org.test.js +++ b/api/src/auth/checks/__tests__/check-user-belongs-to-org.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' import { checkUserBelongsToOrg } from '../check-user-belongs-to-org' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -13,18 +14,21 @@ describe('given the checkUserBelongsToOrg function', () => { let query, drop, truncate, collections, user, org beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -64,8 +68,7 @@ describe('given the checkUserBelongsToOrg function', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) }) it('returns true', async () => { @@ -117,9 +120,7 @@ describe('given the checkUserBelongsToOrg function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckUserBelongsToOrg = checkUserBelongsToOrg({ i18n, @@ -130,11 +131,7 @@ describe('given the checkUserBelongsToOrg function', () => { try { await testCheckUserBelongsToOrg({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error( - `Unable to load affiliation information. Please try again.`, - ), - ) + expect(err).toEqual(new Error(`Unable to load affiliation information. Please try again.`)) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: 123 belongs to org: 123: Error: Database error occurred`, @@ -157,9 +154,7 @@ describe('given the checkUserBelongsToOrg function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckUserBelongsToOrg = checkUserBelongsToOrg({ i18n, @@ -170,11 +165,7 @@ describe('given the checkUserBelongsToOrg function', () => { try { await testCheckUserBelongsToOrg({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error( - `Impossible de charger les informations d'affiliation. Veuillez réessayer.`, - ), - ) + expect(err).toEqual(new Error(`Impossible de charger les informations d'affiliation. Veuillez réessayer.`)) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: 123 belongs to org: 123: Error: Database error occurred`, diff --git a/api-js/src/auth/__tests__/check-user-is-admin-for-user.test.js b/api/src/auth/checks/__tests__/check-user-is-admin-for-user.test.js similarity index 87% rename from api-js/src/auth/__tests__/check-user-is-admin-for-user.test.js rename to api/src/auth/checks/__tests__/check-user-is-admin-for-user.test.js index 8a83bcdbd9..2a14fe5334 100644 --- a/api-js/src/auth/__tests__/check-user-is-admin-for-user.test.js +++ b/api/src/auth/checks/__tests__/check-user-is-admin-for-user.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' import { checkUserIsAdminForUser } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -35,25 +36,27 @@ describe('given the checkUserIsAdminForUser', () => { describe('given a successful call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user1 = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) user2 = await collections.users.save({ userName: 'test.account2@istio.actually.exists', displayName: 'Test Account2', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }) @@ -225,9 +228,7 @@ describe('given the checkUserIsAdminForUser', () => { const testCheck = checkUserIsAdminForUser({ i18n, userKey: user1._key, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), }) try { @@ -235,9 +236,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error('Permission error, not an admin for this user.'), - ) + expect(err).toEqual(Error('Permission error, not an admin for this user.')) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: ${user1._key} has super admin permission for user: test.account2@istio.actually.exists, error: Error: Database error occurred.`, @@ -264,9 +263,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error('Permission error, not an admin for this user.'), - ) + expect(err).toEqual(Error('Permission error, not an admin for this user.')) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: ${user1._key} has admin permission for user: test.account2@istio.actually.exists, error: Error: Database error occurred.`, @@ -294,9 +291,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error('Permission error, not an admin for this user.'), - ) + expect(err).toEqual(Error('Permission error, not an admin for this user.')) } expect(consoleOutput).toEqual([ `Cursor error when checking to see if user: ${user1._key} has super admin permission for user: test.account2@istio.actually.exists, error: Error: Cursor error occurred.`, @@ -329,9 +324,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error('Permission error, not an admin for this user.'), - ) + expect(err).toEqual(Error('Permission error, not an admin for this user.')) } expect(consoleOutput).toEqual([ `Cursor error when checking to see if user: ${user1._key} has admin permission for user: test.account2@istio.actually.exists, error: Error: Cursor error occurred.`, @@ -361,9 +354,7 @@ describe('given the checkUserIsAdminForUser', () => { const testCheck = checkUserIsAdminForUser({ i18n, userKey: user1._key, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), }) try { @@ -371,11 +362,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error( - "Erreur de permission, pas d'administrateur pour cet utilisateur.", - ), - ) + expect(err).toEqual(Error("Erreur de permission, pas d'administrateur pour cet utilisateur.")) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: ${user1._key} has super admin permission for user: test.account2@istio.actually.exists, error: Error: Database error occurred.`, @@ -402,11 +389,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error( - "Erreur de permission, pas d'administrateur pour cet utilisateur.", - ), - ) + expect(err).toEqual(Error("Erreur de permission, pas d'administrateur pour cet utilisateur.")) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: ${user1._key} has admin permission for user: test.account2@istio.actually.exists, error: Error: Database error occurred.`, @@ -434,11 +417,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error( - "Erreur de permission, pas d'administrateur pour cet utilisateur.", - ), - ) + expect(err).toEqual(Error("Erreur de permission, pas d'administrateur pour cet utilisateur.")) } expect(consoleOutput).toEqual([ `Cursor error when checking to see if user: ${user1._key} has super admin permission for user: test.account2@istio.actually.exists, error: Error: Cursor error occurred.`, @@ -471,11 +450,7 @@ describe('given the checkUserIsAdminForUser', () => { userName: 'test.account2@istio.actually.exists', }) } catch (err) { - expect(err).toEqual( - Error( - "Erreur de permission, pas d'administrateur pour cet utilisateur.", - ), - ) + expect(err).toEqual(Error("Erreur de permission, pas d'administrateur pour cet utilisateur.")) } expect(consoleOutput).toEqual([ `Cursor error when checking to see if user: ${user1._key} has admin permission for user: test.account2@istio.actually.exists, error: Error: Cursor error occurred.`, diff --git a/api/src/auth/checks/check-domain-ownership.js b/api/src/auth/checks/check-domain-ownership.js new file mode 100644 index 0000000000..982a78e693 --- /dev/null +++ b/api/src/auth/checks/check-domain-ownership.js @@ -0,0 +1,78 @@ +import { t } from '@lingui/macro' + +export const checkDomainOwnership = + ({ i18n, query, userKey }) => + async ({ domainId }) => { + let userAffiliatedOwnership, ownership + const userKeyString = `users/${userKey}` + + // Check to see if the user is a super admin + let superAdminAffiliationCursor + try { + superAdminAffiliationCursor = await query` + WITH affiliations, organizations, users, domains + LET domainOwnerships = (FOR v, e IN 1..1 ANY ${domainId} ownership RETURN e._from) + LET superAdmin = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission == "super_admin" + RETURN e.from + ) + RETURN { + domainOwnership: (LENGTH(domainOwnerships) > 0 ? true : false), + superAdmin: (LENGTH(superAdmin) > 0 ? true : false) + } + ` + } catch (err) { + console.error( + `Database error when retrieving super admin affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) + } + + let superAdminAffiliation + try { + superAdminAffiliation = await superAdminAffiliationCursor.next() + } catch (err) { + console.error( + `Cursor error when retrieving super admin affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) + } + + if (superAdminAffiliation.superAdmin) { + return !!superAdminAffiliation.domainOwnership + } + + // Get user affiliations and affiliated orgs owning provided domain + try { + userAffiliatedOwnership = await query` + WITH affiliations, domains, organizations, ownership, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET domainOwnerships = (FOR v, e IN 1..1 ANY ${domainId} ownership RETURN v) + LET domainBelongsToVerifiedOrg = POSITION(domainOwnerships[*].verified, true) + LET affiliatedOwnership = INTERSECTION(userAffiliations, domainOwnerships) + RETURN (domainBelongsToVerifiedOrg && hasVerifiedOrgAffiliation) || LENGTH(affiliatedOwnership) > 0 + ` + } catch (err) { + console.error( + `Database error when retrieving affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) + } + + try { + ownership = await userAffiliatedOwnership.next() + } catch (err) { + console.error( + `Cursor error when retrieving affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) + } + + return ownership + } diff --git a/api/src/auth/checks/check-domain-permission.js b/api/src/auth/checks/check-domain-permission.js new file mode 100644 index 0000000000..54d8e300e1 --- /dev/null +++ b/api/src/auth/checks/check-domain-permission.js @@ -0,0 +1,65 @@ +import { t } from '@lingui/macro' + +export const checkDomainPermission = + ({ i18n, query, userKey }) => + async ({ domainId }) => { + let userAffiliatedClaims + const userKeyString = `users/${userKey}` + + // Check to see if the user is a super admin + let superAdminAffiliationCursor + try { + superAdminAffiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission == 'super_admin' + RETURN e.from + ` + } catch (err) { + console.error( + `Database error when retrieving super admin claims for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } + + if (superAdminAffiliationCursor.count > 0) { + return true + } + + // Retrieve user affiliations and affiliated organizations owning provided domain + try { + userAffiliatedClaims = await query` + WITH domains, users, organizations + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET domainOrgClaims = ( + FOR v, e IN 1..1 ANY ${domainId} claims + RETURN v + ) + LET domainBelongsToVerifiedOrg = POSITION(domainOrgClaims[*].verified, true) + LET affiliatedClaims = INTERSECTION(userAffiliations, domainOrgClaims) + RETURN (domainBelongsToVerifiedOrg && hasVerifiedOrgAffiliation) || LENGTH(affiliatedClaims) > 0 + ` + } catch (err) { + console.error( + `Database error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } + + let affiliated + try { + affiliated = await userAffiliatedClaims.next() + } catch (err) { + console.error( + `Cursor error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } + + return affiliated + } diff --git a/api/src/auth/checks/check-org-owner.js b/api/src/auth/checks/check-org-owner.js new file mode 100644 index 0000000000..51fac07950 --- /dev/null +++ b/api/src/auth/checks/check-org-owner.js @@ -0,0 +1,31 @@ +import { t } from '@lingui/macro' + +export const checkOrgOwner = + ({ i18n, query, userKey }) => + async ({ orgId }) => { + const userIdString = `users/${userKey}` + + // find affiliation + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations + FILTER e._to == ${userIdString} + RETURN e.permission == "owner" + ` + } catch (err) { + console.error(`Database error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`) + throw new Error(i18n._(t`Unable to load owner information. Please try again.`)) + } + + let isOrgOwner + try { + isOrgOwner = await affiliationCursor.next() + } catch (err) { + console.error(`Cursor error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`) + throw new Error(i18n._(t`Unable to load owner information. Please try again.`)) + } + + return isOrgOwner + } diff --git a/api/src/auth/checks/check-permission.js b/api/src/auth/checks/check-permission.js new file mode 100644 index 0000000000..33d68b47f9 --- /dev/null +++ b/api/src/auth/checks/check-permission.js @@ -0,0 +1,67 @@ +import { t } from '@lingui/macro' + +export const checkPermission = + ({ i18n, userKey, query }) => + async ({ orgId }) => { + let cursor + const userKeyString = `users/${userKey}` + // Check for super admin + try { + cursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1 INBOUND ${userKeyString} affiliations + FILTER e.permission == "super_admin" + RETURN e.permission + ` + } catch (err) { + console.error(`Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + let permission + try { + permission = await cursor.next() + } catch (err) { + console.error(`Cursor error when checking to see if user ${userKeyString} has super admin permission: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } + + if (permission === 'super_admin') { + return permission + } + + // Check for other permission level + try { + cursor = await query` + WITH affiliations, organizations, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET org = DOCUMENT(${orgId}) + LET orgIsVerified = org.verified + LET userOrgAffiliation = FIRST( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e._from == ${orgId} + RETURN e + ) + RETURN userOrgAffiliation.permission IN ["user", "admin", "owner", "super_admin"] ? userOrgAffiliation.permission + : (orgIsVerified && hasVerifiedOrgAffiliation) ? "user" + : userOrgAffiliation.permission == "pending" ? userOrgAffiliation + : null + ` + } catch (err) { + console.error(`Database error occurred when checking ${userKeyString}'s permission: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + try { + permission = await cursor.next() + } catch (err) { + console.error(`Cursor error when checking ${userKeyString}'s permission: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } + return permission + } diff --git a/api/src/auth/checks/check-super-admin.js b/api/src/auth/checks/check-super-admin.js new file mode 100644 index 0000000000..6dc9c4be19 --- /dev/null +++ b/api/src/auth/checks/check-super-admin.js @@ -0,0 +1,34 @@ +import {t} from '@lingui/macro' + +export const checkSuperAdmin = + ({i18n, userKey, query}) => + async () => { + let cursor + const userKeyString = `users/${userKey}` + // Check for super admin + try { + cursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1 INBOUND ${userKeyString} affiliations + FILTER e.permission == "super_admin" + RETURN e.permission + ` + } catch (err) { + console.error( + `Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`, + ) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } + + let permission + try { + permission = await cursor.next() + } catch (err) { + console.error( + `Cursor error when checking to see if user ${userKeyString} has super admin permission: ${err}`, + ) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } + + return typeof permission !== 'undefined' + } diff --git a/api/src/auth/checks/check-user-belongs-to-org.js b/api/src/auth/checks/check-user-belongs-to-org.js new file mode 100644 index 0000000000..44912ed378 --- /dev/null +++ b/api/src/auth/checks/check-user-belongs-to-org.js @@ -0,0 +1,24 @@ +import { t } from '@lingui/macro' + +export const checkUserBelongsToOrg = + ({ i18n, query, userKey }) => + async ({ orgId }) => { + const userIdString = `users/${userKey}` + + // find affiliation + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations + FILTER e._to == ${userIdString} + FILTER e.permission != "pending" + RETURN e + ` + } catch (err) { + console.error(`Database error when checking to see if user: ${userKey} belongs to org: ${orgId}: ${err}`) + throw new Error(i18n._(t`Unable to load affiliation information. Please try again.`)) + } + + return affiliationCursor.count > 0 + } diff --git a/api-js/src/auth/check-user-is-admin-for-user.js b/api/src/auth/checks/check-user-is-admin-for-user.js similarity index 88% rename from api-js/src/auth/check-user-is-admin-for-user.js rename to api/src/auth/checks/check-user-is-admin-for-user.js index 1b8b89cd4c..77845dd1ba 100644 --- a/api-js/src/auth/check-user-is-admin-for-user.js +++ b/api/src/auth/checks/check-user-is-admin-for-user.js @@ -38,8 +38,8 @@ export const checkUserIsAdminForUser = WITH affiliations, organizations, users LET requestingUserOrgKeys = ( FOR v, e IN 1 INBOUND ${requestingUserId} affiliations - FILTER e.permission == "admin" - RETURN v._key + FILTER e.permission IN ["admin", "owner"] + RETURN v._key ) LET requestedUser = ( @@ -50,6 +50,7 @@ export const checkUserIsAdminForUser = LET requestedUserOrgKeys = ( FOR v, e IN 1 INBOUND requestedUser[0]._id affiliations + FILTER e.permission != "pending" RETURN v._key ) @@ -59,9 +60,7 @@ export const checkUserIsAdminForUser = console.error( `Database error when checking to see if user: ${userKey} has admin permission for user: ${userName}, error: ${err}`, ) - throw new Error( - i18n._(t`Permission error, not an admin for this user.`), - ) + throw new Error(i18n._(t`Permission error, not an admin for this user.`)) } let isAdmin @@ -71,9 +70,7 @@ export const checkUserIsAdminForUser = console.error( `Cursor error when checking to see if user: ${userKey} has admin permission for user: ${userName}, error: ${err}`, ) - throw new Error( - i18n._(t`Permission error, not an admin for this user.`), - ) + throw new Error(i18n._(t`Permission error, not an admin for this user.`)) } return isAdmin diff --git a/api/src/auth/checks/index.js b/api/src/auth/checks/index.js new file mode 100644 index 0000000000..0aaa93dda3 --- /dev/null +++ b/api/src/auth/checks/index.js @@ -0,0 +1,7 @@ +export * from './check-domain-ownership' +export * from './check-domain-permission' +export * from './check-org-owner' +export * from './check-permission' +export * from './check-super-admin' +export * from './check-user-belongs-to-org' +export * from './check-user-is-admin-for-user' diff --git a/api/src/auth/data-source.js b/api/src/auth/data-source.js new file mode 100644 index 0000000000..67389d24b6 --- /dev/null +++ b/api/src/auth/data-source.js @@ -0,0 +1,9 @@ +import { loadPermissionByOrgId, loadDomainPermissionByDomainId, loadOrgOwnerByOrgId } from './loaders' + +export class AuthDataSource { + constructor({ query, userKey, i18n }) { + this.permissionByOrgId = loadPermissionByOrgId({ query, userKey, i18n }) + this.domainPermissionByDomainId = loadDomainPermissionByDomainId({ query, userKey, i18n }) + this.orgOwnerByOrgId = loadOrgOwnerByOrgId({ query, userKey, i18n }) + } +} diff --git a/api/src/auth/guards/__tests__/tfa-required.test.js b/api/src/auth/guards/__tests__/tfa-required.test.js new file mode 100644 index 0000000000..c48657cef0 --- /dev/null +++ b/api/src/auth/guards/__tests__/tfa-required.test.js @@ -0,0 +1,117 @@ +import { setupI18n } from '@lingui/core' + +import { tfaRequired } from '../index' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +describe('given the tfaRequired function', () => { + let i18n, user + + const consoleOutput = [] + const mockedWarn = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.warn = mockedWarn + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful call', () => { + describe('provided a tfa activated user', () => { + beforeEach(() => { + user = { + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: true, + tfaSendMethod: 'email', + } + }) + it('returns true', () => { + const tfaFunc = tfaRequired({}) + + const verifiedUser = tfaFunc({ user }) + + expect(verifiedUser).toBe(true) + }) + }) + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user does not have tfa activated', () => { + beforeEach(() => { + user = { + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + tfaSendMethod: 'none', + } + }) + it('throws an error', () => { + const tfaFunc = tfaRequired({ i18n }) + + try { + tfaFunc({ user }) + } catch (err) { + expect(err).toEqual( + new Error('Verification error. Please activate multi-factor authentication to access content.'), + ) + } + }) + }) + }) + describe('language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['fr'], + messages: { + fr: frenchMessages.messages, + }, + }) + }) + describe('user does not have tfa activated', () => { + beforeEach(() => { + user = { + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + tfaSendMethod: 'none', + } + }) + it.skip('throws an error', () => { + const tfaFunc = tfaRequired({ i18n }) + + try { + tfaFunc({ user }) + } catch (err) { + expect(err).toEqual( + new Error( + "Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu.", + ), + ) + } + }) + }) + }) + }) +}) diff --git a/api-js/src/auth/__tests__/user-required.test.js b/api/src/auth/guards/__tests__/user-required.test.js similarity index 81% rename from api-js/src/auth/__tests__/user-required.test.js rename to api/src/auth/guards/__tests__/user-required.test.js index 1dbd53c446..19c4a72715 100644 --- a/api-js/src/auth/__tests__/user-required.test.js +++ b/api/src/auth/guards/__tests__/user-required.test.js @@ -1,11 +1,12 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { databaseOptions } from '../../../database-options' -import { loadUserByKey, loadUserByUserName } from '../../user/loaders' +import { loadUserByKey, loadUserByUserName } from '../../../user/loaders' import { userRequired } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -26,18 +27,21 @@ describe('given a loadUserByKey dataloader', () => { describe('given a successful call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -91,14 +95,10 @@ describe('given a loadUserByKey dataloader', () => { }) await testUserRequired() } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } - expect(consoleOutput).toEqual([ - `User attempted to access controlled content, but userKey was undefined.`, - ]) + expect(consoleOutput).toEqual([`User attempted to access controlled content, but userKey was undefined.`]) }) }) describe('user cannot be found in database', () => { @@ -113,9 +113,7 @@ describe('given a loadUserByKey dataloader', () => { }) await testUserRequired() } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } expect(consoleOutput).toEqual([ @@ -149,14 +147,10 @@ describe('given a loadUserByKey dataloader', () => { }) await testUserRequired() } catch (err) { - expect(err).toEqual( - new Error("Erreur d'authentification. Veuillez vous connecter."), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } - expect(consoleOutput).toEqual([ - `User attempted to access controlled content, but userKey was undefined.`, - ]) + expect(consoleOutput).toEqual([`User attempted to access controlled content, but userKey was undefined.`]) }) }) describe('user cannot be found in database', () => { @@ -171,9 +165,7 @@ describe('given a loadUserByKey dataloader', () => { }) await testUserRequired() } catch (err) { - expect(err).toEqual( - new Error("Erreur d'authentification. Veuillez vous connecter."), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } expect(consoleOutput).toEqual([ @@ -217,9 +209,7 @@ describe('given a loadUserByKey dataloader', () => { }) await testUserRequired() } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } expect(consoleOutput).toEqual([ @@ -261,9 +251,7 @@ describe('given a loadUserByKey dataloader', () => { }) await testUserRequired() } catch (err) { - expect(err).toEqual( - new Error("Erreur d'authentification. Veuillez vous connecter."), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/auth/__tests__/verified-required.test.js b/api/src/auth/guards/__tests__/verified-required.test.js similarity index 83% rename from api-js/src/auth/__tests__/verified-required.test.js rename to api/src/auth/guards/__tests__/verified-required.test.js index 3c0d6b0a20..4ea31d45de 100644 --- a/api-js/src/auth/__tests__/verified-required.test.js +++ b/api/src/auth/guards/__tests__/verified-required.test.js @@ -1,8 +1,8 @@ import { setupI18n } from '@lingui/core' import { verifiedRequired } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' describe('given the verifiedRequired function', () => { let i18n, user @@ -23,16 +23,15 @@ describe('given the verifiedRequired function', () => { user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: true, } }) it('returns true', () => { const verifiedFunc = verifiedRequired({}) - + const verifiedUser = verifiedFunc({ user }) - + expect(verifiedUser).toBe(true) }) }) @@ -56,21 +55,18 @@ describe('given the verifiedRequired function', () => { user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, } }) it('throws an error', () => { const verifiedFunc = verifiedRequired({ i18n }) - + try { verifiedFunc({ user }) } catch (err) { expect(err).toEqual( - new Error( - 'Verification error. Please verify your account via email to access content.', - ), + new Error('Verification error. Please verify your account via email to access content.'), ) } }) @@ -95,21 +91,18 @@ describe('given the verifiedRequired function', () => { user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, } }) it('throws an error', () => { const verifiedFunc = verifiedRequired({ i18n }) - + try { verifiedFunc({ user }) } catch (err) { expect(err).toEqual( - new Error( - 'Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.', - ), + new Error('Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.'), ) } }) diff --git a/api/src/auth/guards/index.js b/api/src/auth/guards/index.js new file mode 100644 index 0000000000..5a21e03fae --- /dev/null +++ b/api/src/auth/guards/index.js @@ -0,0 +1,4 @@ +export * from './super-admin-required' +export * from './tfa-required' +export * from './user-required' +export * from './verified-required' diff --git a/api/src/auth/guards/super-admin-required.js b/api/src/auth/guards/super-admin-required.js new file mode 100644 index 0000000000..f33e1922b5 --- /dev/null +++ b/api/src/auth/guards/super-admin-required.js @@ -0,0 +1,12 @@ +import { t } from '@lingui/macro' + +export const superAdminRequired = + ({ i18n }) => + ({ user, isSuperAdmin }) => { + if (isSuperAdmin) { + return true + } + + console.warn(`User: ${user._key} attempted to access controlled functionality without sufficient privileges.`) + throw new Error(i18n._(t`Permissions error. You do not have sufficient permissions to access this data.`)) + } diff --git a/api/src/auth/guards/tfa-required.js b/api/src/auth/guards/tfa-required.js new file mode 100644 index 0000000000..8c96a46f09 --- /dev/null +++ b/api/src/auth/guards/tfa-required.js @@ -0,0 +1,18 @@ +import {t} from '@lingui/macro' + +export const tfaRequired = + ({i18n}) => + ({user}) => { + if (user.tfaSendMethod !== 'none') { + return true + } + + console.warn( + `User: ${user._key} attempted to access controlled functionality without multi-factor verification.`, + ) + throw new Error( + i18n._( + t`Verification error. Please activate multi-factor authentication to access content.`, + ), + ) + } diff --git a/api/src/auth/guards/user-required.js b/api/src/auth/guards/user-required.js new file mode 100644 index 0000000000..baa48a8f88 --- /dev/null +++ b/api/src/auth/guards/user-required.js @@ -0,0 +1,32 @@ +import {t} from '@lingui/macro' + +export const userRequired = + ({i18n, userKey, loadUserByKey}) => + async () => { + if (typeof userKey === 'undefined') { + console.warn( + `User attempted to access controlled content, but userKey was undefined.`, + ) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + let user, userDoesNotExist + try { + user = await loadUserByKey.load(userKey) + if (typeof user === 'undefined') { + userDoesNotExist = true + } + } catch (err) { + console.error(`Database error occurred when running userRequired: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + if (userDoesNotExist) { + console.warn( + `User: ${userKey} attempted to access controlled content, but no user is associated with that id.`, + ) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + return user + } diff --git a/api/src/auth/guards/verified-required.js b/api/src/auth/guards/verified-required.js new file mode 100644 index 0000000000..058691e1cb --- /dev/null +++ b/api/src/auth/guards/verified-required.js @@ -0,0 +1,18 @@ +import {t} from '@lingui/macro' + +export const verifiedRequired = + ({i18n}) => + ({user}) => { + if (user.emailValidated) { + return true + } + + console.warn( + `User: ${user._key} attempted to access controlled functionality without verification.`, + ) + throw new Error( + i18n._( + t`Verification error. Please verify your account via email to access content.`, + ), + ) + } diff --git a/api/src/auth/index.js b/api/src/auth/index.js new file mode 100644 index 0000000000..2f77a785a2 --- /dev/null +++ b/api/src/auth/index.js @@ -0,0 +1,4 @@ +export * from './checks' +export * from './guards' +export * from './utils' +export * from './data-source' diff --git a/api/src/auth/loaders/__tests__/load-domain-permission-by-domain-id.test.js b/api/src/auth/loaders/__tests__/load-domain-permission-by-domain-id.test.js new file mode 100644 index 0000000000..60344802b4 --- /dev/null +++ b/api/src/auth/loaders/__tests__/load-domain-permission-by-domain-id.test.js @@ -0,0 +1,206 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import { loadDomainPermissionByDomainId } from '../load-domain-permission-by-domain-id' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the loadDomainPermissionByDomainId loader', () => { + let query, drop, truncate, collections, org, domain, i18n + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful domain permission load', () => { + let user + + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }) + org = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + lastRan: null, + selectors: [], + }) + await collections.claims.save({ _from: org._id, _to: domain._id }) + }) + + afterEach(async () => { + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('when user is affiliated with an org that claims the domain', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + + it('returns true', async () => { + const loader = loadDomainPermissionByDomainId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(domain._id) + expect(result).toBe(true) + }) + }) + + describe('when user has no affiliation with any org claiming the domain', () => { + it('returns false', async () => { + const loader = loadDomainPermissionByDomainId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(domain._id) + expect(result).toBe(false) + }) + }) + + describe('when loading multiple domain IDs in one batch', () => { + let domain2 + + beforeEach(async () => { + domain2 = await collections.domains.save({ + domain: 'test2.gc.ca', + slug: 'test2-gc-ca', + lastRan: null, + selectors: [], + }) + await collections.affiliations.save({ _from: org._id, _to: user._id, permission: 'user' }) + }) + + it('returns correct permissions for each domain in a single batch', async () => { + const loader = loadDomainPermissionByDomainId({ query, userKey: user._key, i18n: {} }) + const [perm1, perm2] = await loader.loadMany([domain._id, domain2._id]) + expect(perm1).toBe(true) + expect(perm2).toBe(false) + }) + }) + }) + + describe('given an unsuccessful domain permission load', () => { + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + + describe('database error on super admin check', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const loader = loadDomainPermissionByDomainId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('domains/1')).rejects.toThrow( + 'Permission check error. Unable to request domain information.', + ) + + expect(consoleOutput).toEqual([ + `Database error when checking super admin permission for user: users/1: Error: Database error occurred.`, + ]) + }) + }) + + describe('database error on batch domain permission check', () => { + it('throws an error', async () => { + const mockQuery = jest + .fn() + .mockResolvedValueOnce({ count: undefined }) + .mockRejectedValue(new Error('Database error occurred.')) + + const loader = loadDomainPermissionByDomainId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('domains/1')).rejects.toThrow( + 'Permission check error. Unable to request domain information.', + ) + + expect(consoleOutput).toEqual([ + `Database error when checking domain permissions for user: users/1: Error: Database error occurred.`, + ]) + }) + }) + + describe('cursor error on batch domain permission check', () => { + it('throws an error', async () => { + const errorCursor = { + forEach() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest + .fn() + .mockResolvedValueOnce({ count: undefined }) + .mockResolvedValue(errorCursor) + + const loader = loadDomainPermissionByDomainId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('domains/1')).rejects.toThrow( + 'Permission check error. Unable to request domain information.', + ) + + expect(consoleOutput).toEqual([ + `Cursor error when checking domain permissions for user: users/1: Error: Cursor error occurred.`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/auth/loaders/__tests__/load-org-owner-by-org-id.test.js b/api/src/auth/loaders/__tests__/load-org-owner-by-org-id.test.js new file mode 100644 index 0000000000..1812c0c96a --- /dev/null +++ b/api/src/auth/loaders/__tests__/load-org-owner-by-org-id.test.js @@ -0,0 +1,200 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import { loadOrgOwnerByOrgId } from '../load-org-owner-by-org-id' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the loadOrgOwnerByOrgId loader', () => { + let query, drop, truncate, collections, user, org, i18n + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful org owner load', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + + afterEach(async () => { + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('when user is the owner', () => { + beforeEach(async () => { + await collections.affiliations.save({ _from: org._id, _to: user._id, permission: 'owner' }) + }) + + it('returns true', async () => { + const loader = loadOrgOwnerByOrgId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(org._id) + expect(result).toBe(true) + }) + }) + + describe('when user is not the owner', () => { + beforeEach(async () => { + await collections.affiliations.save({ _from: org._id, _to: user._id, permission: 'admin' }) + }) + + it('returns false', async () => { + const loader = loadOrgOwnerByOrgId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(org._id) + expect(result).toBe(false) + }) + }) + + describe('when user has no affiliation', () => { + it('returns false', async () => { + const loader = loadOrgOwnerByOrgId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(org._id) + expect(result).toBe(false) + }) + }) + + describe('when loading multiple org IDs in one batch', () => { + let org2 + + beforeEach(async () => { + org2 = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'second-org', + acronym: 'SO', + name: 'Second Organization', + zone: 'FED', + sector: 'SO', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'deuxieme-org', + acronym: 'DO', + name: 'Deuxième Organisation', + zone: 'FED', + sector: 'DO', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ _from: org._id, _to: user._id, permission: 'owner' }) + await collections.affiliations.save({ _from: org2._id, _to: user._id, permission: 'admin' }) + }) + + it('returns correct ownership status for each org in a single batch', async () => { + const loader = loadOrgOwnerByOrgId({ query, userKey: user._key, i18n: {} }) + const [isOwner1, isOwner2] = await loader.loadMany([org._id, org2._id]) + expect(isOwner1).toBe(true) + expect(isOwner2).toBe(false) + }) + }) + }) + + describe('given an unsuccessful org owner load', () => { + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + + describe('database error occurs', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const loader = loadOrgOwnerByOrgId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('organizations/1')).rejects.toThrow( + 'Unable to load owner information. Please try again.', + ) + + expect(consoleOutput).toEqual([ + `Database error when checking org ownership for user: 1: Error: Database error occurred.`, + ]) + }) + }) + + describe('cursor error occurs', () => { + it('throws an error', async () => { + const errorCursor = { + forEach() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest.fn().mockResolvedValue(errorCursor) + const loader = loadOrgOwnerByOrgId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('organizations/1')).rejects.toThrow( + 'Unable to load owner information. Please try again.', + ) + + expect(consoleOutput).toEqual([ + `Cursor error when checking org ownership for user: 1: Error: Cursor error occurred.`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/auth/loaders/__tests__/load-permission-by-org-id.test.js b/api/src/auth/loaders/__tests__/load-permission-by-org-id.test.js new file mode 100644 index 0000000000..145bf1fa77 --- /dev/null +++ b/api/src/auth/loaders/__tests__/load-permission-by-org-id.test.js @@ -0,0 +1,263 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import { loadPermissionByOrgId } from '../load-permission-by-org-id' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the loadPermissionByOrgId loader', () => { + let query, drop, truncate, collections, i18n + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful permission load', () => { + let user, org + + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }) + org = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + + afterEach(async () => { + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('when user is a super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + + it('returns super_admin for all requested org IDs', async () => { + const loader = loadPermissionByOrgId({ query, userKey: user._key, i18n: {} }) + const [result] = await loader.loadMany([org._id]) + expect(result).toEqual('super_admin') + }) + }) + + describe('when user is an admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + }) + + it('returns admin', async () => { + const loader = loadPermissionByOrgId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(org._id) + expect(result).toEqual('admin') + }) + }) + + describe('when user is a regular user', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + + it('returns user', async () => { + const loader = loadPermissionByOrgId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(org._id) + expect(result).toEqual('user') + }) + }) + + describe('when user has no affiliation with the org', () => { + it('returns null', async () => { + const loader = loadPermissionByOrgId({ query, userKey: user._key, i18n: {} }) + const result = await loader.load(org._id) + expect(result).toBeNull() + }) + }) + + describe('when loading multiple org IDs in one batch', () => { + let org2 + + beforeEach(async () => { + org2 = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'second-org', + acronym: 'SO', + name: 'Second Organization', + zone: 'FED', + sector: 'SO', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'deuxieme-org', + acronym: 'DO', + name: 'Deuxième Organisation', + zone: 'FED', + sector: 'DO', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ _from: org._id, _to: user._id, permission: 'admin' }) + await collections.affiliations.save({ _from: org2._id, _to: user._id, permission: 'user' }) + }) + + it('returns correct permissions for each org in a single batch', async () => { + const loader = loadPermissionByOrgId({ query, userKey: user._key, i18n: {} }) + const [perm1, perm2] = await loader.loadMany([org._id, org2._id]) + expect(perm1).toEqual('admin') + expect(perm2).toEqual('user') + }) + }) + }) + + describe('given an unsuccessful permission load', () => { + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + + describe('database error on super admin check', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const loader = loadPermissionByOrgId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('organizations/1')).rejects.toThrow('Authentication error. Please sign in.') + + expect(consoleOutput).toEqual([ + `Database error when checking super admin permission for user: users/1: Error: Database error occurred.`, + ]) + }) + }) + + describe('cursor error on super admin check', () => { + it('throws an error', async () => { + const cursor = { + next() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest.fn().mockResolvedValue(cursor) + const loader = loadPermissionByOrgId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('organizations/1')).rejects.toThrow('Unable to check permission. Please try again.') + + expect(consoleOutput).toEqual([ + `Cursor error when checking super admin permission for user: users/1: Error: Cursor error occurred.`, + ]) + }) + }) + + describe('database error on batch permission check', () => { + it('throws an error', async () => { + const mockQuery = jest + .fn() + .mockResolvedValueOnce({ next: () => undefined }) + .mockRejectedValue(new Error('Database error occurred.')) + + const loader = loadPermissionByOrgId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('organizations/1')).rejects.toThrow('Authentication error. Please sign in.') + + expect(consoleOutput).toEqual([ + `Database error when checking permissions for user: users/1: Error: Database error occurred.`, + ]) + }) + }) + + describe('cursor error on batch permission check', () => { + it('throws an error', async () => { + const errorCursor = { + forEach() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest + .fn() + .mockResolvedValueOnce({ next: () => undefined }) + .mockResolvedValue(errorCursor) + + const loader = loadPermissionByOrgId({ query: mockQuery, userKey: '1', i18n }) + + await expect(loader.load('organizations/1')).rejects.toThrow('Unable to check permission. Please try again.') + + expect(consoleOutput).toEqual([ + `Cursor error when checking permissions for user: users/1: Error: Cursor error occurred.`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/auth/loaders/index.js b/api/src/auth/loaders/index.js new file mode 100644 index 0000000000..ed64fc5701 --- /dev/null +++ b/api/src/auth/loaders/index.js @@ -0,0 +1,3 @@ +export * from './load-permission-by-org-id' +export * from './load-domain-permission-by-domain-id' +export * from './load-org-owner-by-org-id' diff --git a/api/src/auth/loaders/load-domain-permission-by-domain-id.js b/api/src/auth/loaders/load-domain-permission-by-domain-id.js new file mode 100644 index 0000000000..ce2f8b3cef --- /dev/null +++ b/api/src/auth/loaders/load-domain-permission-by-domain-id.js @@ -0,0 +1,65 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadDomainPermissionByDomainId = ({ query, userKey, i18n }) => + new DataLoader(async (domainIds) => { + const userKeyString = `users/${userKey}` + + // Check super admin once for the whole batch (mirrors existing checkDomainPermission logic) + let superAdminCursor + try { + superAdminCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission == 'super_admin' + RETURN e._from + ` + } catch (err) { + console.error(`Database error when checking super admin permission for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } + + if (superAdminCursor.count > 0) { + return domainIds.map(() => true) + } + + // Batch domain permission check across all domain IDs in one query + let cursor + try { + cursor = await query` + WITH affiliations, claims, domains, organizations, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + FOR domainId IN ${domainIds} + LET domainOrgClaims = ( + FOR v, e IN 1..1 ANY domainId claims + RETURN v + ) + LET domainBelongsToVerifiedOrg = POSITION(domainOrgClaims[*].verified, true) + LET affiliatedClaims = INTERSECTION(userAffiliations, domainOrgClaims) + RETURN { + domainId: domainId, + permitted: (domainBelongsToVerifiedOrg && hasVerifiedOrgAffiliation) || LENGTH(affiliatedClaims) > 0 + } + ` + } catch (err) { + console.error(`Database error when checking domain permissions for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } + + const permMap = {} + try { + await cursor.forEach(({ domainId, permitted }) => { + permMap[domainId] = permitted + }) + } catch (err) { + console.error(`Cursor error when checking domain permissions for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } + + return domainIds.map((id) => permMap[id] ?? false) + }) diff --git a/api/src/auth/loaders/load-org-owner-by-org-id.js b/api/src/auth/loaders/load-org-owner-by-org-id.js new file mode 100644 index 0000000000..88232815d6 --- /dev/null +++ b/api/src/auth/loaders/load-org-owner-by-org-id.js @@ -0,0 +1,37 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadOrgOwnerByOrgId = ({ query, userKey, i18n }) => + new DataLoader(async (orgIds) => { + const userIdString = `users/${userKey}` + + let cursor + try { + cursor = await query` + WITH affiliations, organizations, users + LET userId = ${userIdString} + FOR orgId IN ${orgIds} + LET isOwner = FIRST( + FOR v, e IN 1..1 OUTBOUND orgId affiliations + FILTER e._to == userId + RETURN e.permission == "owner" + ) + RETURN { orgId: orgId, isOwner: isOwner == true } + ` + } catch (err) { + console.error(`Database error when checking org ownership for user: ${userKey}: ${err}`) + throw new Error(i18n._(t`Unable to load owner information. Please try again.`)) + } + + const ownerMap = {} + try { + await cursor.forEach(({ orgId, isOwner }) => { + ownerMap[orgId] = isOwner + }) + } catch (err) { + console.error(`Cursor error when checking org ownership for user: ${userKey}: ${err}`) + throw new Error(i18n._(t`Unable to load owner information. Please try again.`)) + } + + return orgIds.map((id) => ownerMap[id] ?? false) + }) diff --git a/api/src/auth/loaders/load-permission-by-org-id.js b/api/src/auth/loaders/load-permission-by-org-id.js new file mode 100644 index 0000000000..cccc12aac5 --- /dev/null +++ b/api/src/auth/loaders/load-permission-by-org-id.js @@ -0,0 +1,77 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadPermissionByOrgId = ({ query, userKey, i18n }) => + new DataLoader(async (orgIds) => { + const userKeyString = `users/${userKey}` + + // Check for super admin once for the whole batch + let superAdminCursor + try { + superAdminCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 INBOUND ${userKeyString} affiliations + FILTER e.permission == "super_admin" + RETURN e.permission + ` + } catch (err) { + console.error(`Database error when checking super admin permission for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + let superAdminPermission + try { + superAdminPermission = await superAdminCursor.next() + } catch (err) { + console.error(`Cursor error when checking super admin permission for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } + + if (superAdminPermission === 'super_admin') { + return orgIds.map(() => 'super_admin') + } + + // Batch permission check across all org IDs in one query + let cursor + try { + cursor = await query` + WITH affiliations, organizations, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + FOR orgId IN ${orgIds} + LET org = DOCUMENT(orgId) + LET orgIsVerified = org.verified + LET userOrgAffiliation = FIRST( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e._from == orgId + RETURN e + ) + RETURN { + orgId: orgId, + permission: userOrgAffiliation.permission IN ["user", "admin", "owner", "super_admin"] ? userOrgAffiliation.permission + : (orgIsVerified && hasVerifiedOrgAffiliation) ? "user" + : userOrgAffiliation.permission == "pending" ? userOrgAffiliation + : null + } + ` + } catch (err) { + console.error(`Database error when checking permissions for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } + + const permMap = {} + try { + await cursor.forEach(({ orgId, permission }) => { + permMap[orgId] = permission + }) + } catch (err) { + console.error(`Cursor error when checking permissions for user: ${userKeyString}: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } + + return orgIds.map((id) => permMap[id] ?? null) + }) diff --git a/api-js/src/auth/__tests__/generate-jwt.test.js b/api/src/auth/utils/__tests__/generate-jwt.test.js similarity index 94% rename from api-js/src/auth/__tests__/generate-jwt.test.js rename to api/src/auth/utils/__tests__/generate-jwt.test.js index d37cbe83bb..33fc30e388 100644 --- a/api-js/src/auth/__tests__/generate-jwt.test.js +++ b/api/src/auth/utils/__tests__/generate-jwt.test.js @@ -20,7 +20,7 @@ describe('tokenize()', () => { const token = tokenize({ secret: 'foo' }) const decoded = jwt.verify(token, 'foo') - expect(decoded.exp - decoded.iat).toEqual(3600) + expect(decoded.exp - decoded.iat).toEqual(900) }) }) }) diff --git a/api/src/auth/utils/__tests__/salted-hash.test.js b/api/src/auth/utils/__tests__/salted-hash.test.js new file mode 100644 index 0000000000..66808d4e3e --- /dev/null +++ b/api/src/auth/utils/__tests__/salted-hash.test.js @@ -0,0 +1,14 @@ +import {saltedHash} from '../salted-hash' + +describe('saltedHash()', () => { + describe('when passed data and a salt', () => { + it('hashses them', () => { + const data = 'secret-data' + const salt = 'secret-salt' + + const hashed = saltedHash(salt)(data) + + expect(hashed).toEqual('b3982ae24c3cca26431152c5ebfff694') + }) + }) +}) diff --git a/api/src/auth/utils/__tests__/verify-jwt.test.js b/api/src/auth/utils/__tests__/verify-jwt.test.js new file mode 100644 index 0000000000..f53cf66a7f --- /dev/null +++ b/api/src/auth/utils/__tests__/verify-jwt.test.js @@ -0,0 +1,105 @@ +import jwt from 'jsonwebtoken' +import {setupI18n} from '@lingui/core' + +import {verifyToken} from '../index' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +const {AUTHENTICATED_KEY} = process.env + +describe('given a encoded token', () => { + let consoleOutput = [] + let i18n + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + beforeEach(() => { + console.info = mockedInfo + console.warn = mockedWarn + }) + + afterEach(() => { + consoleOutput = [] + }) + describe('token can be decoded and verified', () => { + it('returns the parameters', () => { + const parameters = { + userKey: 1, + } + const token = jwt.sign({parameters}, String(AUTHENTICATED_KEY), { + algorithm: 'HS256', + }) + + const testVerify = verifyToken({i18n}) + const decoded = testVerify({token}) + expect(decoded.userKey).toEqual(1) + }) + }) + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: {plurals: {}}, + fr: {plurals: {}}, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('if the secret does not match', () => { + it('raises an error', () => { + const parameters = { + userKey: 1, + } + const token = jwt.sign({parameters}, 'superSecretKey', { + algorithm: 'HS256', + }) + + const testVerify = verifyToken({i18n}) + expect(() => { + testVerify({token}) + }).toThrow(Error('Invalid token, please sign in.')) + expect(consoleOutput).toEqual([ + `JWT was attempted to be verified but secret was incorrect.`, + ]) + }) + }) + }) + describe('language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: {plurals: {}}, + fr: {plurals: {}}, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('if the secret does not match', () => { + it('raises an error', () => { + const parameters = { + userKey: 1, + } + const token = jwt.sign({parameters}, 'superSecretKey', { + algorithm: 'HS256', + }) + + const testVerify = verifyToken({i18n}) + expect(() => { + testVerify({token}) + }).toThrow(Error('Jeton invalide, veuillez vous connecter.')) + expect(consoleOutput).toEqual([ + `JWT was attempted to be verified but secret was incorrect.`, + ]) + }) + }) + }) +}) diff --git a/api/src/auth/utils/generate-jwt.js b/api/src/auth/utils/generate-jwt.js new file mode 100644 index 0000000000..f6a7b2d936 --- /dev/null +++ b/api/src/auth/utils/generate-jwt.js @@ -0,0 +1,12 @@ +import jwt from 'jsonwebtoken' + +const { AUTHENTICATED_KEY } = process.env + +export const tokenize = ({ expiresIn = '15m', parameters = {}, secret = String(AUTHENTICATED_KEY) }) => + jwt.sign( + { + parameters, + }, + secret, + { algorithm: 'HS256', expiresIn: expiresIn }, + ) diff --git a/api/src/auth/utils/index.js b/api/src/auth/utils/index.js new file mode 100644 index 0000000000..d6160f558e --- /dev/null +++ b/api/src/auth/utils/index.js @@ -0,0 +1,3 @@ +export * from './generate-jwt' +export * from './salted-hash' +export * from './verify-jwt' diff --git a/api/src/auth/utils/salted-hash.js b/api/src/auth/utils/salted-hash.js new file mode 100644 index 0000000000..659e3e87b7 --- /dev/null +++ b/api/src/auth/utils/salted-hash.js @@ -0,0 +1,7 @@ +import crypto from 'crypto' + +export const saltedHash = (salt) => (data) => { + const saltedData = data + salt + + return crypto.createHash('md5').update(saltedData).digest('hex') +} diff --git a/api/src/auth/utils/verify-jwt.js b/api/src/auth/utils/verify-jwt.js new file mode 100644 index 0000000000..e6c2204442 --- /dev/null +++ b/api/src/auth/utils/verify-jwt.js @@ -0,0 +1,18 @@ +import { t } from '@lingui/macro' +import { GraphQLError } from 'graphql' +import jwt from 'jsonwebtoken' + +const { AUTHENTICATED_KEY } = process.env + +export const verifyToken = + ({ i18n }) => + ({ token, secret = String(AUTHENTICATED_KEY) }) => { + let decoded + try { + decoded = jwt.verify(token, secret) + } catch (err) { + console.warn('JWT was attempted to be verified but secret was incorrect.') + throw new GraphQLError(i18n._(t`Invalid token, please sign in.`), { extensions: { code: 'UNAUTHENTICATED' } }) + } + return decoded.parameters + } diff --git a/api/src/collection-names.js b/api/src/collection-names.js new file mode 100644 index 0000000000..7916ff5314 --- /dev/null +++ b/api/src/collection-names.js @@ -0,0 +1,33 @@ +export const collectionNames = [ + 'users', + 'organizations', + 'domains', + 'guidanceTags', + 'dkimGuidanceTags', + 'dmarcGuidanceTags', + 'spfGuidanceTags', + 'httpsGuidanceTags', + 'sslGuidanceTags', + 'chartSummaries', + 'dmarcSummaries', + 'aggregateGuidanceTags', + 'scanSummaryCriteria', + 'chartSummaryCriteria', + 'scanSummaries', + 'affiliations', + 'claims', + 'favourites', + 'auditLogs', + 'ownership', + 'domainsToDmarcSummaries', + 'dns', + 'domainsDNS', + 'domainsWeb', + 'web', + 'webScan', + 'webToWebScans', + 'selectors', + 'domainsToSelectors', + 'organizationSummaries', + 'tags', +] diff --git a/api/src/create-context.js b/api/src/create-context.js new file mode 100644 index 0000000000..ee4bcc9f05 --- /dev/null +++ b/api/src/create-context.js @@ -0,0 +1,166 @@ +import bcrypt from 'bcryptjs' +import moment from 'moment' +import fetch from 'isomorphic-fetch' +import { v4 as uuidv4 } from 'uuid' +import jwt from 'jsonwebtoken' + +import { loadUserByKey } from './user/loaders' +import { cleanseInput, decryptPhoneNumber, slugify } from './validators' +import { initializeLoaders } from './initialize-loaders' +import { SummariesDataSource } from './summaries' +import { DnsScanDataSource } from './dns-scan' +import { WebScanDataSource } from './web-scan' +import { AuditLogsDataSource } from './audit-logs' +import { AdditionalFindingsDataSource } from './additional-findings' +import { GuidanceTagDataSource } from './guidance-tag' +import { OrganizationDataSource } from './organization' +import { TagsDataSource } from './tags' +import { + AuthDataSource, + checkDomainOwnership, + checkDomainPermission, + checkOrgOwner, + checkPermission, + checkSuperAdmin, + checkUserBelongsToOrg, + checkUserIsAdminForUser, + tokenize, + saltedHash, + superAdminRequired, + userRequired, + verifiedRequired, + verifyToken, + tfaRequired, +} from './auth' +import { + notifyClient, + sendAuthEmail, + sendAuthTextMsg, + sendInviteRequestEmail, + sendOrgInviteCreateAccount, + sendOrgInviteEmail, + sendPasswordResetEmail, + sendUpdatedUserNameEmail, + sendVerificationEmail, + sendRoleChangeEmail, +} from './notify' + +export async function createContext({ + query, + db, + transaction, + collections, + publish, + req: request, + res: response, + i18n, + language, + loginRequiredBool, + salt, +}) { + const verify = verifyToken({ i18n }) + + // Get user id from token + let userKey + const token = request.headers.authorization || '' + if (token !== '') { + userKey = verify({ token }).userKey + } else { + userKey = 'NO_USER' + } + + return { + query, + db, + transaction, + collections, + publish, + i18n, + request, + response, + userKey, + language, + moment, + fetch, + uuidv4, + jwt, + auth: { + bcrypt, + checkDomainOwnership: checkDomainOwnership({ + i18n, + userKey, + query, + auth: { loginRequiredBool }, + }), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey, + query, + auth: { loginRequiredBool }, + }), + checkOrgOwner: checkOrgOwner({ i18n, userKey, query }), + checkPermission: checkPermission({ i18n, userKey, query }), + checkSuperAdmin: checkSuperAdmin({ i18n, userKey, query }), + checkUserBelongsToOrg: checkUserBelongsToOrg({ i18n, query, userKey }), + checkUserIsAdminForUser: checkUserIsAdminForUser({ + i18n, + userKey, + query, + }), + loginRequiredBool, + tokenize, + tfaRequired: tfaRequired({ i18n }), + saltedHash: saltedHash(salt), + superAdminRequired: superAdminRequired({ i18n }), + userRequired: userRequired({ + i18n, + userKey, + loadUserByKey: loadUserByKey({ query, userKey, i18n }), + }), + verifiedRequired: verifiedRequired({ i18n }), + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + decryptPhoneNumber, + slugify, + }, + notify: { + sendAuthEmail: sendAuthEmail({ notifyClient, i18n }), + sendAuthTextMsg: sendAuthTextMsg({ notifyClient, i18n }), + sendInviteRequestEmail: sendInviteRequestEmail({ notifyClient, i18n }), + sendOrgInviteCreateAccount: sendOrgInviteCreateAccount({ + notifyClient, + i18n, + }), + sendOrgInviteEmail: sendOrgInviteEmail({ notifyClient, i18n }), + sendPasswordResetEmail: sendPasswordResetEmail({ notifyClient, i18n }), + sendUpdatedUserNameEmail: sendUpdatedUserNameEmail({ + notifyClient, + i18n, + }), + sendVerificationEmail: sendVerificationEmail({ notifyClient, i18n }), + sendRoleChangeEmail: sendRoleChangeEmail({ notifyClient, i18n }), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey, i18n }), + summaries: new SummariesDataSource({ query, userKey, cleanseInput, i18n }), + additionalFindings: new AdditionalFindingsDataSource({ query, userKey, i18n, language: request.language }), + auditLogs: new AuditLogsDataSource({ query, userKey, cleanseInput, i18n, transaction, collections }), + dnsScan: new DnsScanDataSource({ query, userKey, cleanseInput, i18n }), + guidanceTag: new GuidanceTagDataSource({ query, userKey, i18n, language: request.language, cleanseInput }), + organization: new OrganizationDataSource({ query, userKey, i18n, language: request.language, cleanseInput, loginRequiredBool, transaction, collections }), + tags: new TagsDataSource({ query, userKey, i18n, language: request.language, transaction, collections }), + webScan: new WebScanDataSource({ query, userKey, cleanseInput, i18n }), + }, + loaders: initializeLoaders({ + query, + userKey, + i18n, + language: request.language, + cleanseInput, + loginRequiredBool, + moment, + }), + } +} diff --git a/api/src/create-i18n.js b/api/src/create-i18n.js new file mode 100644 index 0000000000..863c8804d5 --- /dev/null +++ b/api/src/create-i18n.js @@ -0,0 +1,16 @@ +import { setupI18n } from '@lingui/core' +import englishMessages from './locale/en/messages' +import frenchMessages from './locale/fr/messages' + +export const createI18n = (language = 'en') => { + const i18n = setupI18n({ + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + + i18n.load({ en: englishMessages.messages, fr: frenchMessages.messages }) + i18n.activate(language) + return i18n +} diff --git a/api-js/src/dmarc-summaries/index.js b/api/src/dmarc-summaries/index.js similarity index 100% rename from api-js/src/dmarc-summaries/index.js rename to api/src/dmarc-summaries/index.js diff --git a/api-js/src/dmarc-summaries/inputs/dmarc-summary-order.js b/api/src/dmarc-summaries/inputs/dmarc-summary-order.js similarity index 81% rename from api-js/src/dmarc-summaries/inputs/dmarc-summary-order.js rename to api/src/dmarc-summaries/inputs/dmarc-summary-order.js index fed4221efc..0df9fd9981 100644 --- a/api-js/src/dmarc-summaries/inputs/dmarc-summary-order.js +++ b/api/src/dmarc-summaries/inputs/dmarc-summary-order.js @@ -7,11 +7,11 @@ export const dmarcSummaryOrder = new GraphQLInputObjectType({ description: 'Ordering options for dmarc summary connections.', fields: () => ({ field: { - type: GraphQLNonNull(DmarcSummaryOrderField), + type: new GraphQLNonNull(DmarcSummaryOrderField), description: 'The field to order dmarc summaries by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/dmarc-summaries/inputs/index.js b/api/src/dmarc-summaries/inputs/index.js similarity index 100% rename from api-js/src/dmarc-summaries/inputs/index.js rename to api/src/dmarc-summaries/inputs/index.js diff --git a/api/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js b/api/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js new file mode 100644 index 0000000000..ef668cf691 --- /dev/null +++ b/api/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { dmarcSummaryOrder } from '../dmarc-summary-order' +import { OrderDirection, DmarcSummaryOrderField } from '../../../enums' + +describe('given the dmarcSummaryOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = dmarcSummaryOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = dmarcSummaryOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(DmarcSummaryOrderField)) + }) + }) +}) diff --git a/api/src/dmarc-summaries/loaders/__tests__/load-all-verified-rua-domains.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-all-verified-rua-domains.test.js new file mode 100644 index 0000000000..dbb7fd37d7 --- /dev/null +++ b/api/src/dmarc-summaries/loaders/__tests__/load-all-verified-rua-domains.test.js @@ -0,0 +1,27 @@ +import { loadAllVerifiedRuaDomains } from '../load-all-verified-rua-domains' + +describe('loadAllVerifiedRuaDomains', () => { + let query, userKey, i18n + + beforeEach(() => { + query = jest.fn().mockReturnValue({ + all: jest.fn().mockResolvedValue([{ key: 'org1', domains: ['domain1', 'domain2'] }]), + }) + userKey = 'userKey' + i18n = { _: jest.fn().mockReturnValue('error message') } + }) + + it('returns the correct data when the database query is successful', async () => { + const loader = loadAllVerifiedRuaDomains({ query, userKey, i18n }) + const result = await loader() + expect(result).toEqual([{ key: 'org1', domains: ['domain1', 'domain2'] }]) + }) + + it('throws an error when the database query fails', async () => { + query = jest.fn().mockImplementation(() => { + throw new Error() + }) + const loader = loadAllVerifiedRuaDomains({ query, userKey, i18n }) + await expect(loader()).rejects.toThrow('error message') + }) +}) diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-dkim-failure-connections-by-sum-id.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-dkim-failure-connections-by-sum-id.test.js similarity index 89% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-dkim-failure-connections-by-sum-id.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-dkim-failure-connections-by-sum-id.test.js index 9e3ac4b612..4bdaf7eda7 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-dkim-failure-connections-by-sum-id.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-dkim-failure-connections-by-sum-id.test.js @@ -1,26 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadDkimFailConnectionsBySumId } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadDkimFailConnectionsBySumId loader', () => { - let query, - drop, - truncate, - collections, - i18n, - user, - dmarcSummary, - dkimFailure1, - dkimFailure2 + let query, drop, truncate, collections, i18n, user, dmarcSummary, dkimFailure1, dkimFailure2 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -36,16 +29,19 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { describe('given a successful load', () => { beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -449,11 +445,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `DkimFailureTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `DkimFailureTable` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -479,11 +471,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `DkimFailureTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `DkimFailureTable` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -495,9 +483,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDkimFailConnectionsBySumId({ query, userKey: user._key, @@ -515,11 +501,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -531,9 +513,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDkimFailConnectionsBySumId({ query, userKey: user._key, @@ -551,11 +531,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -583,11 +559,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DKIM failure data. Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load DKIM failure data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -598,9 +570,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) describe('given a database error occurs', () => { it('returns an error message', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadDkimFailConnectionsBySumId({ query: mockedQuery, @@ -618,9 +588,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DKIM failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DKIM failure data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -653,9 +621,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DKIM failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DKIM failure data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -822,9 +788,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro.'), ) } @@ -852,9 +816,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`last` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro.', - ), + new Error('`last` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro.'), ) } @@ -867,9 +829,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDkimFailConnectionsBySumId({ query, userKey: user._key, @@ -888,9 +848,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -903,9 +861,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDkimFailConnectionsBySumId({ query, userKey: user._key, @@ -924,9 +880,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -955,11 +909,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec DKIM. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec DKIM. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -970,9 +920,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { }) describe('given a database error occurs', () => { it('returns an error message', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadDkimFailConnectionsBySumId({ query: mockedQuery, @@ -990,11 +938,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec DKIM. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec DKIM. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -1027,11 +971,7 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec DKIM. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec DKIM. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-failure-connections-by-sum-id.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-failure-connections-by-sum-id.test.js similarity index 89% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-failure-connections-by-sum-id.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-dmarc-failure-connections-by-sum-id.test.js index 226e2cc139..230f562303 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-failure-connections-by-sum-id.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-failure-connections-by-sum-id.test.js @@ -1,26 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadDmarcFailConnectionsBySumId } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadDmarcFailConnectionsBySumId loader', () => { - let query, - drop, - truncate, - collections, - i18n, - user, - dmarcSummary, - dmarcFailure1, - dmarcFailure2 + let query, drop, truncate, collections, i18n, user, dmarcSummary, dmarcFailure1, dmarcFailure2 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -37,16 +30,19 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { describe('given a successful load', () => { beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -444,9 +440,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { await connectionLoader({ ...connectionArgs }) } catch (err) { expect(err).toEqual( - new Error( - '`first` on the `DmarcFailureTable` connection cannot be less than zero.', - ), + new Error('`first` on the `DmarcFailureTable` connection cannot be less than zero.'), ) } @@ -472,11 +466,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { try { await connectionLoader({ ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `DmarcFailureTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `DmarcFailureTable` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -488,9 +478,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcFailConnectionsBySumId({ query, userKey: user._key, @@ -508,11 +496,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -524,9 +508,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcFailConnectionsBySumId({ query, userKey: user._key, @@ -544,11 +526,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -575,11 +553,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { try { await connectionLoader({ ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DMARC failure data. Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load DMARC failure data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -590,9 +564,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { }) describe('given a database error', () => { it('returns an error message', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadDmarcFailConnectionsBySumId({ query: mockedQuery, @@ -610,9 +582,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DMARC failure data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -645,9 +615,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DMARC failure data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -809,9 +777,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { await connectionLoader({ ...connectionArgs }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro.'), ) } @@ -838,9 +804,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { await connectionLoader({ ...connectionArgs }) } catch (err) { expect(err).toEqual( - new Error( - '`last` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro.', - ), + new Error('`last` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro.'), ) } @@ -853,9 +817,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcFailConnectionsBySumId({ query, userKey: user._key, @@ -874,9 +836,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -889,9 +849,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcFailConnectionsBySumId({ query, userKey: user._key, @@ -910,9 +868,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -940,11 +896,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { try { await connectionLoader({ ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec DMARC. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec DMARC. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -955,9 +907,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { }) describe('given a database error', () => { it('returns an error message', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadDmarcFailConnectionsBySumId({ query: mockedQuery, @@ -975,11 +925,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec DMARC. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec DMARC. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -1012,11 +958,7 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec DMARC. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec DMARC. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-connections-by-user-id.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-connections-by-user-id.test.js similarity index 75% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-connections-by-user-id.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-connections-by-user-id.test.js index 7e129064cd..6fdcaca5f3 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-connections-by-user-id.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-connections-by-user-id.test.js @@ -1,31 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' -import { - loadDmarcSummaryConnectionsByUserId, - loadDmarcSummaryByKey, -} from '../index' +import { loadDmarcSummaryConnectionsByUserId, loadDmarcSummaryByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadDmarcSummaryConnectionsByUserId function', () => { - let query, - drop, - truncate, - collections, - org, - i18n, - user, - domain1, - domain2, - dmarcSummary1, - dmarcSummary2 + let query, drop, truncate, collections, org, i18n, user, domain1, domain2, dmarcSummary1, dmarcSummary2 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -43,18 +31,21 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -90,7 +81,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { domain1 = await collections.domains.save({ domain: 'test1.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], }) await collections.ownership.save({ _to: domain1._id, @@ -99,7 +90,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { domain2 = await collections.domains.save({ domain: 'test2.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], }) await collections.ownership.save({ _to: domain2._id, @@ -175,10 +166,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -203,10 +193,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -224,10 +211,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -252,10 +238,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -273,10 +256,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -300,10 +282,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -321,10 +300,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -348,10 +326,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -371,19 +346,15 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) it('returns the filtered dmarc summaries', async () => { const summaryLoader = loadDmarcSummaryByKey({ query }) - const expectedSummaries = await summaryLoader.loadMany([ - dmarcSummary1._key, - dmarcSummary2._key, - ]) + const expectedSummaries = await summaryLoader.loadMany([dmarcSummary1._key, dmarcSummary2._key]) const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -408,10 +379,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -432,10 +400,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -454,10 +421,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -467,14 +431,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -491,10 +449,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -513,10 +470,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -526,14 +480,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -552,10 +500,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -574,10 +521,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -587,14 +531,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -611,10 +549,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -633,10 +570,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -646,14 +580,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -672,10 +600,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -694,10 +621,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -707,14 +631,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -731,10 +649,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -753,10 +670,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -766,14 +680,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -792,10 +700,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -814,10 +721,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -827,14 +731,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -851,10 +749,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -873,10 +770,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -886,14 +780,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -912,10 +800,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -934,10 +821,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -947,14 +831,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -971,10 +849,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -993,10 +870,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1006,14 +880,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1032,10 +900,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1054,10 +921,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1067,14 +931,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1091,10 +949,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1113,10 +970,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1126,14 +980,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1152,10 +1000,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1174,10 +1021,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1187,14 +1031,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1211,10 +1049,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1233,10 +1070,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1246,14 +1080,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1272,10 +1100,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1294,10 +1121,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1307,14 +1131,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1331,10 +1149,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1353,10 +1170,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1366,14 +1180,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1392,10 +1200,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1414,10 +1221,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1427,14 +1231,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1451,10 +1249,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1473,10 +1270,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1486,14 +1280,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1512,10 +1300,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1534,10 +1321,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1547,14 +1331,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1571,10 +1349,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1593,10 +1370,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1606,14 +1380,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1634,10 +1402,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1656,10 +1423,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1669,14 +1433,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1693,10 +1451,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1715,10 +1472,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1728,14 +1482,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1754,10 +1502,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1776,10 +1523,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1789,14 +1533,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1813,10 +1551,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1835,10 +1572,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1848,14 +1582,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1874,10 +1602,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1896,10 +1623,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -1909,14 +1633,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -1933,10 +1651,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1955,10 +1672,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -1968,14 +1682,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -1994,10 +1702,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2016,10 +1723,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2029,14 +1733,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2053,10 +1751,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2075,10 +1772,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2088,14 +1782,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2114,10 +1802,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2136,10 +1823,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2149,14 +1833,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2173,10 +1851,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2195,10 +1872,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2208,14 +1882,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2234,10 +1902,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2256,10 +1923,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2269,14 +1933,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2293,10 +1951,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2315,10 +1972,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2328,14 +1982,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2354,10 +2002,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2376,10 +2023,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2389,14 +2033,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2413,10 +2051,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2435,10 +2072,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2448,14 +2082,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2474,10 +2102,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2496,10 +2123,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2509,14 +2133,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2533,10 +2151,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2555,10 +2172,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2568,14 +2182,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2594,10 +2202,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2616,10 +2223,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2629,14 +2233,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2653,10 +2251,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2675,10 +2272,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2688,14 +2282,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2714,10 +2302,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2736,10 +2323,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), node: { ...expectedSummaries[0], }, @@ -2749,14 +2333,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), }, } @@ -2773,10 +2351,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2795,10 +2372,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), node: { ...expectedSummaries[1], }, @@ -2808,14 +2382,8 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), - endCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[1]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2835,10 +2403,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2869,10 +2436,58 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'dmarcSummary', - expectedSummaries[0]._key, - ), + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + }, + } + + expect(summaries).toEqual(expectedStructure) + }) + }) + describe('isAffiliated is set to true', () => { + it('returns dmarc summaries', async () => { + const expectedSummaries = await loadDmarcSummaryByKey({ + query, + }).loadMany([dmarcSummary1._key, dmarcSummary2._key]) + + const connectionLoader = loadDmarcSummaryConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n: {}, + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), + }) + + const connectionArgs = { + first: 10, + period: 'thirtyDays', + year: '2021', + isAffiliated: true, + } + + const summaries = await connectionLoader({ ...connectionArgs }) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), + node: { + ...expectedSummaries[0], + }, + }, + { + cursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), + node: { + ...expectedSummaries[1], + }, + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('dmarcSummary', expectedSummaries[0]._key), endCursor: toGlobalId('dmarcSummary', expectedSummaries[1]._key), }, } @@ -2889,6 +2504,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) @@ -2939,10 +2555,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2972,10 +2587,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3008,10 +2622,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3042,10 +2655,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3078,10 +2690,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3095,11 +2706,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `DmarcSummaries` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `DmarcSummaries` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadDmarcSummaryConnectionsByUserId.`, @@ -3112,10 +2719,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3129,11 +2735,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `DmarcSummaries` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `DmarcSummaries` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadDmarcSummaryConnectionsByUserId.`, @@ -3144,17 +2746,14 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3168,11 +2767,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -3184,17 +2779,14 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3208,11 +2800,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -3229,10 +2817,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3246,9 +2833,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'You must provide a `period` value to access the `DmarcSummaries` connection.', - ), + new Error('You must provide a `period` value to access the `DmarcSummaries` connection.'), ) } expect(consoleOutput).toEqual([ @@ -3262,10 +2847,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3279,9 +2863,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'You must provide a `year` value to access the `DmarcSummaries` connection.', - ), + new Error('You must provide a `year` value to access the `DmarcSummaries` connection.'), ) } expect(consoleOutput).toEqual([ @@ -3292,18 +2874,15 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { describe('given a database error', () => { describe('while querying for domain information', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3316,11 +2895,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DMARC summary data. Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load DMARC summary data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -3343,10 +2918,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3359,11 +2933,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load DMARC summary data. Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load DMARC summary data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -3396,10 +2966,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3429,10 +2998,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3465,10 +3033,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3499,10 +3066,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3535,10 +3101,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3553,9 +3118,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro.'), ) } expect(consoleOutput).toEqual([ @@ -3569,10 +3132,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3587,9 +3149,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`last` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro.', - ), + new Error('`last` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro.'), ) } expect(consoleOutput).toEqual([ @@ -3601,17 +3161,14 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3626,9 +3183,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -3641,17 +3196,14 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3666,9 +3218,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -3686,10 +3236,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3703,9 +3252,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.', - ), + new Error('Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.'), ) } expect(consoleOutput).toEqual([ @@ -3719,10 +3266,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3736,9 +3282,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.', - ), + new Error('Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.'), ) } expect(consoleOutput).toEqual([ @@ -3749,18 +3293,15 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { describe('given a database error', () => { describe('while querying for domain information', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadDmarcSummaryConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3774,9 +3315,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), + new Error('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.'), ) } @@ -3800,10 +3339,9 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3817,9 +3355,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), + new Error('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.'), ) } diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-edge-by-domain-id-period.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-edge-by-domain-id-period.test.js similarity index 83% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-edge-by-domain-id-period.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-edge-by-domain-id-period.test.js index 364804302d..e0661ce692 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-edge-by-domain-id-period.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-sum-edge-by-domain-id-period.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadDmarcSummaryEdgeByDomainIdAndPeriod } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -26,22 +27,25 @@ describe('given the loadDmarcSummaryEdgeByDomainIdAndPeriod loader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) - + dmarcSummary = await collections.dmarcSummaries.save({ detailTables: { dkimFailure: [], @@ -107,27 +111,23 @@ describe('given the loadDmarcSummaryEdgeByDomainIdAndPeriod loader', () => { }) describe('given a database error', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) - + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) + const loader = loadDmarcSummaryEdgeByDomainIdAndPeriod({ query: mockedQuery, userKey: user._key, i18n, }) - + try { await loader({ domainId: 'domains/1', startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC summary data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DMARC summary data. Please try again.')) } - + expect(consoleOutput).toEqual([ `Database error occurred when user: ${user._key} attempted to load dmarc summaries for domain: domains/1, period: thirtyDays, Error: Database error occurred`, ]) @@ -141,24 +141,22 @@ describe('given the loadDmarcSummaryEdgeByDomainIdAndPeriod loader', () => { }, } const mockedQuery = jest.fn().mockReturnValueOnce(cursor) - + const loader = loadDmarcSummaryEdgeByDomainIdAndPeriod({ query: mockedQuery, userKey: user._key, i18n, }) - + try { await loader({ domainId: 'domains/1', startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC summary data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DMARC summary data. Please try again.')) } - + expect(consoleOutput).toEqual([ `Cursor error occurred when user: ${user._key} attempted to load dmarc summaries for domain: domains/1, period: thirtyDays, Error: Cursor error occurred.`, ]) @@ -182,29 +180,23 @@ describe('given the loadDmarcSummaryEdgeByDomainIdAndPeriod loader', () => { }) describe('given a database error', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) - + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) + const loader = loadDmarcSummaryEdgeByDomainIdAndPeriod({ query: mockedQuery, userKey: user._key, i18n, }) - + try { await loader({ domainId: 'domains/1', startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.')) } - + expect(consoleOutput).toEqual([ `Database error occurred when user: ${user._key} attempted to load dmarc summaries for domain: domains/1, period: thirtyDays, Error: Database error occurred`, ]) @@ -218,26 +210,22 @@ describe('given the loadDmarcSummaryEdgeByDomainIdAndPeriod loader', () => { }, } const mockedQuery = jest.fn().mockReturnValueOnce(cursor) - + const loader = loadDmarcSummaryEdgeByDomainIdAndPeriod({ query: mockedQuery, userKey: user._key, i18n, }) - + try { await loader({ domainId: 'domains/1', startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.')) } - + expect(consoleOutput).toEqual([ `Cursor error occurred when user: ${user._key} attempted to load dmarc summaries for domain: domains/1, period: thirtyDays, Error: Cursor error occurred.`, ]) diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-summary-by-key.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-summary-by-key.test.js similarity index 82% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-summary-by-key.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-dmarc-summary-by-key.test.js index 785de0e275..33626679d4 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-dmarc-summary-by-key.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-dmarc-summary-by-key.test.js @@ -1,22 +1,16 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadDmarcSummaryByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadDmarcSummaryByKey dataloader', () => { - let query, - drop, - truncate, - collections, - i18n, - domain, - dmarcSummary1, - dmarcSummary2 + let query, drop, truncate, collections, i18n, domain, dmarcSummary1, dmarcSummary2 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -30,11 +24,15 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -104,13 +102,13 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { it('returns a single dmarc summary', async () => { const expectedCursor = await query` FOR summary IN dmarcSummaries - SORT summary._key ASC + SORT summary._key ASC LIMIT 1 LET edge = ( FOR v, e IN 1..1 ANY summary._id domainsToDmarcSummaries RETURN e ) - + RETURN { _id: summary._id, _key: summary._key, @@ -127,9 +125,7 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { const expectedSummary = await expectedCursor.next() expectedSummary.domainKey = domain._key - const summary = await loadDmarcSummaryByKey({ query }).load( - expectedSummary._key, - ) + const summary = await loadDmarcSummaryByKey({ query }).load(expectedSummary._key) expect(summary).toEqual(expectedSummary) }) @@ -144,7 +140,7 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { FOR v, e IN 1..1 ANY summary._id domainsToDmarcSummaries RETURN e ) - + RETURN { _id: summary._id, _key: summary._key, @@ -165,9 +161,7 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { expectedSummaries.push(temp) } - const summaries = await loadDmarcSummaryByKey({ query }).loadMany( - summaryKeys, - ) + const summaries = await loadDmarcSummaryByKey({ query }).loadMany(summaryKeys) expect(summaries).toEqual(expectedSummaries) }) @@ -190,18 +184,12 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { - await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load( - '1234', - ) + await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load('1234') } catch (err) { - expect(err).toEqual( - new Error('Unable to find DMARC summary data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to find DMARC summary data. Please try again.')) } expect(consoleOutput).toEqual([ `Database error occurred when user: 1234 running loadDmarcSummaryByKey: Error: Database error occurred.`, @@ -218,13 +206,9 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { query = jest.fn().mockReturnValue(cursor) try { - await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load( - '1234', - ) + await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load('1234') } catch (err) { - expect(err).toEqual( - new Error('Unable to find DMARC summary data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to find DMARC summary data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -250,20 +234,12 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { - await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load( - '1234', - ) + await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load('1234') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ `Database error occurred when user: 1234 running loadDmarcSummaryByKey: Error: Database error occurred.`, @@ -280,15 +256,9 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { query = jest.fn().mockReturnValue(cursor) try { - await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load( - '1234', - ) + await loadDmarcSummaryByKey({ query, userKey: '1234', i18n }).load('1234') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-full-pass-connections-by-sum-id.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-full-pass-connections-by-sum-id.test.js similarity index 89% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-full-pass-connections-by-sum-id.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-full-pass-connections-by-sum-id.test.js index e4e1d55d61..16bc47873f 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-full-pass-connections-by-sum-id.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-full-pass-connections-by-sum-id.test.js @@ -1,26 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadFullPassConnectionsBySumId } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadFullPassConnectionsBySumId loader', () => { - let query, - drop, - truncate, - collections, - i18n, - user, - dmarcSummary, - fullPass1, - fullPass2 + let query, drop, truncate, collections, i18n, user, dmarcSummary, fullPass1, fullPass2 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -37,22 +30,25 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) - + fullPass1 = { sourceIpAddress: '123.456.78.91', envelopeFrom: 'envelope.from', @@ -64,7 +60,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', id: 1, } - + fullPass2 = { sourceIpAddress: '123.456.78.91', envelopeFrom: 'envelope.from', @@ -76,7 +72,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', id: 2, } - + dmarcSummary = await collections.dmarcSummaries.save({ detailTables: { dkimFailure: [], @@ -112,15 +108,15 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 100, after: toGlobalId('fullPass', 1), summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -139,7 +135,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -151,15 +147,15 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 100, before: toGlobalId('fullPass', 2), summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -178,7 +174,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -190,14 +186,14 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -216,7 +212,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -228,14 +224,14 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 1, summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -254,7 +250,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -262,21 +258,21 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { describe('given there are no full passes to load', () => { it('returns no full pass connections', async () => { await truncate() - + const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { last: 1, summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [], totalCount: 0, @@ -287,7 +283,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: '', }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -317,7 +313,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -332,7 +328,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadFullPassConnectionsBySumId.`, ]) @@ -346,7 +342,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -363,7 +359,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadFullPassConnectionsBySumId.`, ]) @@ -378,7 +374,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -394,7 +390,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -408,7 +404,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -424,7 +420,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -440,7 +436,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -450,13 +446,9 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `FullPassTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `FullPassTable` connection cannot be less than zero.')) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadFullPassConnectionsBySumId.`, ]) @@ -470,7 +462,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -480,13 +472,9 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `FullPassTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `FullPassTable` connection cannot be less than zero.')) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadFullPassConnectionsBySumId.`, ]) @@ -496,31 +484,25 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -532,31 +514,25 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { last: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -575,7 +551,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 1, } @@ -584,11 +560,9 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load full pass data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load full pass data. Please try again.')) } - + expect(consoleOutput).toEqual([ `SummaryId was undefined when user: ${user._key} attempted to load full passes in loadFullPassConnectionsBySumId.`, ]) @@ -597,17 +571,15 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) describe('given a database error occurs', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -617,11 +589,9 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load full pass data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load full pass data. Please try again.')) } - + expect(consoleOutput).toEqual([ `Database error occurred while user: ${user._key} was trying to gather full passes in loadFullPassConnectionsBySumId, error: Error: Database error occurred.`, ]) @@ -635,14 +605,14 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }, } const query = jest.fn().mockReturnValueOnce(cursor) - + const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -652,11 +622,9 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load full pass data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load full pass data. Please try again.')) } - + expect(consoleOutput).toEqual([ `Cursor error occurred while user: ${user._key} was trying to gather full passes in loadFullPassConnectionsBySumId, error: Error: Cursor error occurred.`, ]) @@ -687,7 +655,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -702,7 +670,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadFullPassConnectionsBySumId.`, ]) @@ -716,7 +684,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -733,7 +701,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadFullPassConnectionsBySumId.`, ]) @@ -748,7 +716,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -764,7 +732,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -778,7 +746,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -794,7 +762,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -810,7 +778,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -821,12 +789,10 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `FullPassTable` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `FullPassTable` ne peut être inférieur à zéro.'), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadFullPassConnectionsBySumId.`, ]) @@ -840,7 +806,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -850,13 +816,9 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `FullPassTable` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `FullPassTable` ne peut être inférieur à zéro.')) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadFullPassConnectionsBySumId.`, ]) @@ -866,30 +828,26 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -902,30 +860,26 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { last: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -945,7 +899,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 1, } @@ -955,12 +909,10 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de charger les données complètes de la passe. Veuillez réessayer.', - ), + new Error('Impossible de charger les données complètes de la passe. Veuillez réessayer.'), ) } - + expect(consoleOutput).toEqual([ `SummaryId was undefined when user: ${user._key} attempted to load full passes in loadFullPassConnectionsBySumId.`, ]) @@ -969,17 +921,15 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) describe('given a database error occurs', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -990,12 +940,10 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de charger les données complètes de la passe. Veuillez réessayer.', - ), + new Error('Impossible de charger les données complètes de la passe. Veuillez réessayer.'), ) } - + expect(consoleOutput).toEqual([ `Database error occurred while user: ${user._key} was trying to gather full passes in loadFullPassConnectionsBySumId, error: Error: Database error occurred.`, ]) @@ -1009,14 +957,14 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }, } const query = jest.fn().mockReturnValueOnce(cursor) - + const connectionLoader = loadFullPassConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -1027,12 +975,10 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de charger les données complètes de la passe. Veuillez réessayer.', - ), + new Error('Impossible de charger les données complètes de la passe. Veuillez réessayer.'), ) } - + expect(consoleOutput).toEqual([ `Cursor error occurred while user: ${user._key} was trying to gather full passes in loadFullPassConnectionsBySumId, error: Error: Cursor error occurred.`, ]) diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-spf-failure-connections-by-sum-id.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-spf-failure-connections-by-sum-id.test.js similarity index 89% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-spf-failure-connections-by-sum-id.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-spf-failure-connections-by-sum-id.test.js index 7da251b6e1..a781d5f949 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-spf-failure-connections-by-sum-id.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-spf-failure-connections-by-sum-id.test.js @@ -1,26 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadSpfFailureConnectionsBySumId } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadSpfFailureConnectionsBySumId loader', () => { - let query, - drop, - truncate, - collections, - i18n, - user, - dmarcSummary, - spfFailure1, - spfFailure2 + let query, drop, truncate, collections, i18n, user, dmarcSummary, spfFailure1, spfFailure2 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -37,22 +30,25 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) - + spfFailure1 = { sourceIpAddress: '123.456.78.91', envelopeFrom: 'envelope.from', @@ -65,7 +61,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', guidance: '', } - + spfFailure2 = { sourceIpAddress: '123.456.78.91', envelopeFrom: 'envelope.from', @@ -78,7 +74,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', guidance: '', } - + dmarcSummary = await collections.dmarcSummaries.save({ detailTables: { dkimFailure: [], @@ -114,15 +110,15 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 100, after: toGlobalId('spfFail', 1), summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -141,7 +137,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -153,15 +149,15 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 100, before: toGlobalId('spfFail', 2), summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -180,7 +176,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -192,14 +188,14 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -218,7 +214,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -230,14 +226,14 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 1, summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [ { @@ -256,7 +252,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -264,21 +260,21 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { describe('given there are no spf failures to load', () => { it('returns no spf failure connections', async () => { await truncate() - + const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 1, summaryId: dmarcSummary._id, } - + const summaries = await connectionLoader({ ...connectionArgs }) - + const expectedStructure = { edges: [], totalCount: 0, @@ -289,7 +285,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: '', }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -319,7 +315,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -334,7 +330,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSpfFailureConnectionsBySumId.`, ]) @@ -348,7 +344,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -365,7 +361,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfFailureConnectionsBySumId.`, ]) @@ -380,7 +376,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -396,7 +392,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -410,7 +406,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -426,7 +422,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -442,7 +438,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -452,13 +448,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `SpfFailureTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `SpfFailureTable` connection cannot be less than zero.')) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadSpfFailureConnectionsBySumId.`, ]) @@ -472,7 +464,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -482,13 +474,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `SpfFailureTable` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `SpfFailureTable` connection cannot be less than zero.')) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadSpfFailureConnectionsBySumId.`, ]) @@ -498,31 +486,25 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -534,31 +516,25 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { last: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -577,7 +553,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 10, } @@ -586,11 +562,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load SPF failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load SPF failure data. Please try again.')) } - + expect(consoleOutput).toEqual([ `SummaryId was undefined when user: ${user._key} attempted to load spf failures in loadSpfFailureConnectionsBySumId.`, ]) @@ -599,17 +573,15 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }) describe('given a database error', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -619,11 +591,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load SPF failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load SPF failure data. Please try again.')) } - + expect(consoleOutput).toEqual([ `Database error occurred while user: ${user._key} was trying to gather spf failures in loadSpfFailureConnectionsBySumId, error: Error: Database error occurred.`, ]) @@ -637,14 +607,14 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }, } const query = jest.fn().mockReturnValueOnce(cursor) - + const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -654,11 +624,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load SPF failure data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load SPF failure data. Please try again.')) } - + expect(consoleOutput).toEqual([ `Cursor error occurred while user: ${user._key} was trying to gather spf failures in loadSpfFailureConnectionsBySumId, error: Error: Cursor error occurred.`, ]) @@ -689,7 +657,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -704,7 +672,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} did not have either \`first\` or \`last\` arguments set for: loadSpfFailureConnectionsBySumId.`, ]) @@ -718,7 +686,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -735,7 +703,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfFailureConnectionsBySumId.`, ]) @@ -750,7 +718,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -766,7 +734,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -780,7 +748,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -796,7 +764,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -812,7 +780,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -823,12 +791,10 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro.'), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set below zero for: loadSpfFailureConnectionsBySumId.`, ]) @@ -842,7 +808,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -853,12 +819,10 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`last` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro.', - ), + new Error('`last` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro.'), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set below zero for: loadSpfFailureConnectionsBySumId.`, ]) @@ -868,30 +832,26 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { describe('first or last argument is not set to a number', () => { describe('first argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -904,30 +864,26 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }) describe('last argument is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { last: invalidInput, summaryId: '', } - + try { await connectionLoader({ ...connectionArgs, }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -947,7 +903,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 10, } @@ -956,13 +912,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec SPF. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec SPF. Veuillez réessayer.")) } - + expect(consoleOutput).toEqual([ `SummaryId was undefined when user: ${user._key} attempted to load spf failures in loadSpfFailureConnectionsBySumId.`, ]) @@ -971,17 +923,15 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }) describe('given a database error', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -991,13 +941,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec SPF. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec SPF. Veuillez réessayer.")) } - + expect(consoleOutput).toEqual([ `Database error occurred while user: ${user._key} was trying to gather spf failures in loadSpfFailureConnectionsBySumId, error: Error: Database error occurred.`, ]) @@ -1011,14 +957,14 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { }, } const query = jest.fn().mockReturnValueOnce(cursor) - + const connectionLoader = loadSpfFailureConnectionsBySumId({ query, userKey: user._key, cleanseInput, i18n, }) - + const connectionArgs = { first: 50, summaryId: '', @@ -1028,13 +974,9 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger les données d'échec SPF. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger les données d'échec SPF. Veuillez réessayer.")) } - + expect(consoleOutput).toEqual([ `Cursor error occurred while user: ${user._key} was trying to gather spf failures in loadSpfFailureConnectionsBySumId, error: Error: Cursor error occurred.`, ]) diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-start-date-from-period.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-start-date-from-period.test.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-start-date-from-period.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-start-date-from-period.test.js diff --git a/api-js/src/dmarc-summaries/loaders/__tests__/load-yearly-dmarc-sum-edges.test.js b/api/src/dmarc-summaries/loaders/__tests__/load-yearly-dmarc-sum-edges.test.js similarity index 84% rename from api-js/src/dmarc-summaries/loaders/__tests__/load-yearly-dmarc-sum-edges.test.js rename to api/src/dmarc-summaries/loaders/__tests__/load-yearly-dmarc-sum-edges.test.js index c0ff5a7337..38bd63605e 100644 --- a/api-js/src/dmarc-summaries/loaders/__tests__/load-yearly-dmarc-sum-edges.test.js +++ b/api/src/dmarc-summaries/loaders/__tests__/load-yearly-dmarc-sum-edges.test.js @@ -1,23 +1,16 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadDmarcYearlySumEdge } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the loadDmarcYearlySumEdge loader', () => { - let query, - drop, - truncate, - collections, - i18n, - user, - dmarcSummary1, - dmarcSummary2, - dmarcSummary3 + let query, drop, truncate, collections, i18n, user, dmarcSummary1, dmarcSummary2, dmarcSummary3 const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -35,18 +28,21 @@ describe('given the loadDmarcYearlySumEdge loader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -150,9 +146,7 @@ describe('given the loadDmarcYearlySumEdge loader', () => { }) describe('given a database error', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const loader = loadDmarcYearlySumEdge({ query: mockedQuery, @@ -166,9 +160,7 @@ describe('given the loadDmarcYearlySumEdge loader', () => { startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC summary data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DMARC summary data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -197,9 +189,7 @@ describe('given the loadDmarcYearlySumEdge loader', () => { startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load DMARC summary data. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load DMARC summary data. Please try again.')) } expect(consoleOutput).toEqual([ @@ -225,9 +215,7 @@ describe('given the loadDmarcYearlySumEdge loader', () => { }) describe('given a database error', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const loader = loadDmarcYearlySumEdge({ query: mockedQuery, @@ -241,11 +229,7 @@ describe('given the loadDmarcYearlySumEdge loader', () => { startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -274,11 +258,7 @@ describe('given the loadDmarcYearlySumEdge loader', () => { startDate: 'thirtyDays', }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api/src/dmarc-summaries/loaders/index.js b/api/src/dmarc-summaries/loaders/index.js new file mode 100644 index 0000000000..c5cb745484 --- /dev/null +++ b/api/src/dmarc-summaries/loaders/index.js @@ -0,0 +1,10 @@ +export * from './load-dkim-failure-connections-by-sum-id' +export * from './load-dmarc-failure-connections-by-sum-id' +export * from './load-dmarc-sum-connections-by-user-id' +export * from './load-dmarc-sum-edge-by-domain-id-period' +export * from './load-dmarc-summary-by-key' +export * from './load-full-pass-connections-by-sum-id' +export * from './load-spf-failure-connections-by-sum-id' +export * from './load-start-date-from-period' +export * from './load-yearly-dmarc-sum-edges' +export * from './load-all-verified-rua-domains' diff --git a/api/src/dmarc-summaries/loaders/load-all-verified-rua-domains.js b/api/src/dmarc-summaries/loaders/load-all-verified-rua-domains.js new file mode 100644 index 0000000000..7ac89a22c1 --- /dev/null +++ b/api/src/dmarc-summaries/loaders/load-all-verified-rua-domains.js @@ -0,0 +1,31 @@ +import { t } from '@lingui/macro' + +export const loadAllVerifiedRuaDomains = + ({ query, userKey, i18n }) => + async () => { + let verifiedRuaDomains + try { + verifiedRuaDomains = ( + await query` + FOR org IN organizations + FILTER org.verified == true + SORT org.orgDetails.en.acronym ASC + LET domains = ( + FOR domain,claim IN 1..1 OUTBOUND org claims + FILTER domain.archived != true + FILTER domain.ignoreRua != true + FILTER domain.rcode != "NXDOMAIN" + FILTER domain.hasCyberRua == true + SORT domain.domain ASC + RETURN domain.domain + ) + RETURN { key: CONCAT(org.orgDetails.en.acronym, "-", org.orgDetails.fr.acronym), domains } + ` + ).all() + } catch (err) { + console.error(`Database error occurred when user: ${userKey} was trying to load verified rua domains: ${err}`) + throw new Error(i18n._(t`Unable to load verified rua domains. Please try again.`)) + } + + return verifiedRuaDomains + } diff --git a/api-js/src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js b/api/src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js rename to api/src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js diff --git a/api-js/src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js b/api/src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js rename to api/src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js diff --git a/api-js/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js b/api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js similarity index 87% rename from api-js/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js rename to api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js index d3fa86cf2b..941985cf69 100644 --- a/api-js/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js +++ b/api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js @@ -4,40 +4,18 @@ import { t } from '@lingui/macro' export const loadDmarcSummaryConnectionsByUserId = ({ query, userKey, cleanseInput, i18n, loadStartDateFromPeriod }) => - async ({ - after, - before, - first, - last, - period, - year, - orderBy, - isSuperAdmin, - search, - }) => { + async ({ after, before, first, last, period, year, orderBy, isSuperAdmin, search, isAffiliated }) => { const userDBId = `users/${userKey}` if (typeof period === 'undefined') { - console.warn( - `User: ${userKey} did not have \`period\` argument set for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`period\` value to access the \`DmarcSummaries\` connection.`, - ), - ) + console.warn(`User: ${userKey} did not have \`period\` argument set for: loadDmarcSummaryConnectionsByUserId.`) + throw new Error(i18n._(t`You must provide a \`period\` value to access the \`DmarcSummaries\` connection.`)) } const cleansedPeriod = cleanseInput(period) if (typeof year === 'undefined') { - console.warn( - `User: ${userKey} did not have \`year\` argument set for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`year\` value to access the \`DmarcSummaries\` connection.`, - ), - ) + console.warn(`User: ${userKey} did not have \`year\` argument set for: loadDmarcSummaryConnectionsByUserId.`) + throw new Error(i18n._(t`You must provide a \`year\` value to access the \`DmarcSummaries\` connection.`)) } const cleansedYear = cleanseInput(year) @@ -189,9 +167,7 @@ export const loadDmarcSummaryConnectionsByUserId = `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, ) throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`DmarcSummaries\` connection is not supported.`, - ), + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`DmarcSummaries\` connection is not supported.`), ) } else if (typeof first === 'number' || typeof last === 'number') { /* istanbul ignore else */ @@ -200,11 +176,7 @@ export const loadDmarcSummaryConnectionsByUserId = console.warn( `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcSummaryConnectionsByUserId.`, ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`DmarcSummaries\` connection cannot be less than zero.`, - ), - ) + throw new Error(i18n._(t`\`${argSet}\` on the \`DmarcSummaries\` connection cannot be less than zero.`)) } else if (first > 100 || last > 100) { const argSet = typeof first !== 'undefined' ? 'first' : 'last' const amount = typeof first !== 'undefined' ? first : last @@ -227,9 +199,7 @@ export const loadDmarcSummaryConnectionsByUserId = console.warn( `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDmarcSummaryConnectionsByUserId.`, ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) } let hasNextPageFilter = aql`FILTER TO_NUMBER(summary._key) > TO_NUMBER(LAST(retrievedSummaries)._key)` @@ -346,61 +316,69 @@ export const loadDmarcSummaryConnectionsByUserId = sortString = aql`ASC` } - let domainQuery = aql`` let searchDomainFilter = aql`` - if (typeof search !== 'undefined') { + if (typeof search !== 'undefined' && search !== '') { search = cleanseInput(search) - domainQuery = aql` - LET tokenArr = TOKENS(${search}, "space-delimiter-analyzer") - LET searchedDomains = ( - FOR token IN tokenArr - FOR domain IN domainSearch - SEARCH ANALYZER(domain.domain LIKE CONCAT("%", token, "%"), "space-delimiter-analyzer") - RETURN domain._id - ) - ` - searchDomainFilter = aql`FILTER domainId IN searchedDomains` + searchDomainFilter = aql`FILTER userDomain.domain LIKE LOWER(CONCAT("%", ${search}, "%"))` } - let domainIdQueries + let domainQueries if (isSuperAdmin) { - domainIdQueries = aql` + domainQueries = aql` WITH affiliations, dmarcSummaries, domains, domainsToDmarcSummaries, organizations, ownership, users, domainSearch - LET domainIds = UNIQUE(FLATTEN( + LET userDomains = UNIQUE(FLATTEN( LET ids = [] LET orgIds = (FOR org IN organizations RETURN org._id) FOR orgId IN orgIds - LET claimDomainIds = (FOR v, e IN 1..1 OUTBOUND orgId ownership RETURN v._id) - RETURN APPEND(ids, claimDomainIds) + LET claimUserDomains = (FOR v, e IN 1..1 OUTBOUND orgId ownership RETURN v) + RETURN APPEND(ids, claimUserDomains) )) ` + } else if (isAffiliated) { + domainQueries = aql` + WITH affiliations, dmarcSummaries, domains, domainsToDmarcSummaries, organizations, ownership, users, domainSearch + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET userDomains = UNIQUE( + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key + FOR v, e IN 1..1 OUTBOUND org._id ownership + RETURN v + ) + ` } else { - domainIdQueries = aql` + domainQueries = aql` WITH affiliations, dmarcSummaries, domains, domainsToDmarcSummaries, organizations, ownership, users, domainSearch - LET domainIds = UNIQUE(FLATTEN( - LET ids = [] - LET orgIds = (FOR v, e IN 1..1 ANY ${userDBId} affiliations RETURN e._from) - FOR orgId IN orgIds - LET claimDomainIds = (FOR v, e IN 1..1 OUTBOUND orgId ownership RETURN v._id) - RETURN APPEND(ids, claimDomainIds) - )) + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET userDomains = UNIQUE( + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key || (hasVerifiedOrgAffiliation == true && org.verified == true) + FOR v, e IN 1..1 OUTBOUND org._id ownership + RETURN v + ) ` } let requestedSummaryInfo try { requestedSummaryInfo = await query` - ${domainIdQueries} - - ${domainQuery} + ${domainQueries} ${afterVar} ${beforeVar} LET summaryIds = ( - FOR domainId IN domainIds + FOR userDomain IN userDomains ${searchDomainFilter} - FOR v, e IN 1..1 ANY domainId domainsToDmarcSummaries + FOR v, e IN 1..1 ANY userDomain._id domainsToDmarcSummaries FILTER e.startDate == ${startDate} RETURN e._to ) @@ -468,9 +446,7 @@ export const loadDmarcSummaryConnectionsByUserId = console.error( `Database error occurred while user: ${userKey} was trying to gather dmarc summaries in loadDmarcSummaryConnectionsByUserId, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to load DMARC summary data. Please try again.`), - ) + throw new Error(i18n._(t`Unable to load DMARC summary data. Please try again.`)) } let summariesInfo @@ -480,9 +456,7 @@ export const loadDmarcSummaryConnectionsByUserId = console.error( `Cursor error occurred while user: ${userKey} was trying to gather dmarc summaries in loadDmarcSummaryConnectionsByUserId, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to load DMARC summary data. Please try again.`), - ) + throw new Error(i18n._(t`Unable to load DMARC summary data. Please try again.`)) } if (summariesInfo.summaries.length === 0) { diff --git a/api-js/src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js b/api/src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js rename to api/src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js diff --git a/api-js/src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js b/api/src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js rename to api/src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js diff --git a/api-js/src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js b/api/src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js rename to api/src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js diff --git a/api-js/src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js b/api/src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js rename to api/src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js diff --git a/api-js/src/dmarc-summaries/loaders/load-start-date-from-period.js b/api/src/dmarc-summaries/loaders/load-start-date-from-period.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-start-date-from-period.js rename to api/src/dmarc-summaries/loaders/load-start-date-from-period.js diff --git a/api-js/src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js b/api/src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js similarity index 100% rename from api-js/src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js rename to api/src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/category-percentages.test.js b/api/src/dmarc-summaries/objects/__tests__/category-percentages.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/category-percentages.test.js rename to api/src/dmarc-summaries/objects/__tests__/category-percentages.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/category-totals.test.js b/api/src/dmarc-summaries/objects/__tests__/category-totals.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/category-totals.test.js rename to api/src/dmarc-summaries/objects/__tests__/category-totals.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/detail-tables.test.js b/api/src/dmarc-summaries/objects/__tests__/detail-tables.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/detail-tables.test.js rename to api/src/dmarc-summaries/objects/__tests__/detail-tables.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/dkim-failure-table-connection.test.js b/api/src/dmarc-summaries/objects/__tests__/dkim-failure-table-connection.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/dkim-failure-table-connection.test.js rename to api/src/dmarc-summaries/objects/__tests__/dkim-failure-table-connection.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js b/api/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js similarity index 81% rename from api-js/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js rename to api/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js index 245a901a9c..133de70e0f 100644 --- a/api-js/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js +++ b/api/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js @@ -1,10 +1,4 @@ -import { - GraphQLID, - GraphQLBoolean, - GraphQLString, - GraphQLInt, - GraphQLNonNull, -} from 'graphql' +import { GraphQLID, GraphQLBoolean, GraphQLString, GraphQLInt, GraphQLNonNull } from 'graphql' import { toGlobalId } from 'graphql-relay' import { dkimFailureTableType } from '../dkim-failure-table' @@ -16,7 +10,7 @@ describe('given the dkimFailureTable gql object', () => { const demoType = dkimFailureTableType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a dkimAligned field', () => { const demoType = dkimFailureTableType.getFields() @@ -90,89 +84,71 @@ describe('given the dkimFailureTable gql object', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('dkimFail', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('dkimFail', 1)) }) }) describe('testing the dkimAligned resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect(demoType.dkimAligned.resolve({ dkimAligned: true })).toEqual( - true, - ) + expect(demoType.dkimAligned.resolve({ dkimAligned: true })).toEqual(true) }) }) describe('testing the dkimDomains resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect( - demoType.dkimDomains.resolve({ dkimDomains: 'dkimDomains' }), - ).toEqual('dkimDomains') + expect(demoType.dkimDomains.resolve({ dkimDomains: 'dkimDomains' })).toEqual('dkimDomains') }) }) describe('testing the dkimResults resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect( - demoType.dkimResults.resolve({ dkimResults: 'dkimResults' }), - ).toEqual('dkimResults') + expect(demoType.dkimResults.resolve({ dkimResults: 'dkimResults' })).toEqual('dkimResults') }) }) describe('testing the dkimSelectors resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect( - demoType.dkimSelectors.resolve({ dkimSelectors: 'dkimSelectors' }), - ).toEqual('dkimSelectors') + expect(demoType.dkimSelectors.resolve({ dkimSelectors: 'dkimSelectors' })).toEqual('dkimSelectors') }) }) describe('testing the dnsHost resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual( - 'dnsHost', - ) + expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual('dnsHost') }) }) describe('testing the envelopeFrom resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect( - demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' }), - ).toEqual('envelopeFrom') + expect(demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' })).toEqual('envelopeFrom') }) }) describe('testing the guidance resolver', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect(demoType.guidance.resolve({ guidance: 'guidance' })).toEqual( - 'guidance', - ) + expect(demoType.guidance.resolve({ guidance: 'guidance' })).toEqual('guidance') }) }) describe('testing the guidanceTag resolver', () => { describe('guidance is not null', () => { it('returns resolved value', async () => { const demoType = dkimFailureTableType.getFields() - + const expectedResults = { - _id: 'aggregateGuidanceTags/agg1', + _id: 'guidanceTags/agg1', _key: 'agg1', _rev: 'rev', _type: 'guidanceTag', guidance: 'cool guidance for issue', id: 'agg1', - refLinksGuide: [ - { description: 'Link Description', ref_link: 'www.link.ca' }, - ], + refLinksGuide: [{ description: 'Link Description', ref_link: 'www.link.ca' }], refLinksTechnical: [ { description: 'Tech link description', @@ -182,16 +158,14 @@ describe('given the dkimFailureTable gql object', () => { tagId: 'agg1', tagName: 'cool-tag-name', } - + expect( await demoType.guidanceTag.resolve( { guidance: 'agg1' }, {}, { - loaders: { - loadAggregateGuidanceTagByTagId: { - load: jest.fn().mockReturnValue(expectedResults), - }, + dataSources: { + guidanceTag: { byTagId: jest.fn().mockReturnValue([expectedResults]) }, }, }, ), @@ -201,18 +175,16 @@ describe('given the dkimFailureTable gql object', () => { describe('guidance is null', () => { it('returns an empty obj', async () => { const demoType = dkimFailureTableType.getFields() - + const expectedResults = {} - + expect( await demoType.guidanceTag.resolve( { guidance: null }, {}, { - loaders: { - loadAggregateGuidanceTagByTagId: { - load: jest.fn().mockReturnValue(expectedResults), - }, + dataSources: { + guidanceTag: { byTagId: jest.fn().mockReturnValue(expectedResults) }, }, }, ), @@ -224,9 +196,7 @@ describe('given the dkimFailureTable gql object', () => { it('returns resolved value', () => { const demoType = dkimFailureTableType.getFields() - expect( - demoType.headerFrom.resolve({ headerFrom: 'headerFrom' }), - ).toEqual('headerFrom') + expect(demoType.headerFrom.resolve({ headerFrom: 'headerFrom' })).toEqual('headerFrom') }) }) describe('testing test sourceIpAddress resolver', () => { diff --git a/api-js/src/dmarc-summaries/objects/__tests__/dmarc-failure-table-connection.test.js b/api/src/dmarc-summaries/objects/__tests__/dmarc-failure-table-connection.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/dmarc-failure-table-connection.test.js rename to api/src/dmarc-summaries/objects/__tests__/dmarc-failure-table-connection.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js b/api/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js similarity index 83% rename from api-js/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js rename to api/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js index 23660b64ba..770eea2cf6 100644 --- a/api-js/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js +++ b/api/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js @@ -9,7 +9,7 @@ describe('given the dmarcFailureTable gql object', () => { const demoType = dmarcFailureTableType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a dkimDomains field', () => { const demoType = dmarcFailureTableType.getFields() @@ -72,63 +72,49 @@ describe('given the dmarcFailureTable gql object', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('dmarcFail', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('dmarcFail', 1)) }) }) describe('testing the dkimDomains resolver', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect( - demoType.dkimDomains.resolve({ dkimDomains: 'dkimDomains' }), - ).toEqual('dkimDomains') + expect(demoType.dkimDomains.resolve({ dkimDomains: 'dkimDomains' })).toEqual('dkimDomains') }) }) describe('testing the dkimSelectors resolver', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect( - demoType.dkimSelectors.resolve({ dkimSelectors: 'dkimSelectors' }), - ).toEqual('dkimSelectors') + expect(demoType.dkimSelectors.resolve({ dkimSelectors: 'dkimSelectors' })).toEqual('dkimSelectors') }) }) describe('testing the disposition resolver', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect( - demoType.disposition.resolve({ disposition: 'disposition' }), - ).toEqual('disposition') + expect(demoType.disposition.resolve({ disposition: 'disposition' })).toEqual('disposition') }) }) describe('testing the dnsHost resolver', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual( - 'dnsHost', - ) + expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual('dnsHost') }) }) describe('testing the envelopeFrom resolver', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect( - demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' }), - ).toEqual('envelopeFrom') + expect(demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' })).toEqual('envelopeFrom') }) }) describe('testing the headerFrom resolver', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect( - demoType.headerFrom.resolve({ headerFrom: 'headerFrom' }), - ).toEqual('headerFrom') + expect(demoType.headerFrom.resolve({ headerFrom: 'headerFrom' })).toEqual('headerFrom') }) }) describe('testing the sourceIpAddress resolver', () => { @@ -146,9 +132,7 @@ describe('given the dmarcFailureTable gql object', () => { it('returns the resolved result', () => { const demoType = dmarcFailureTableType.getFields() - expect( - demoType.spfDomains.resolve({ spfDomains: 'spfDomains' }), - ).toEqual('spfDomains') + expect(demoType.spfDomains.resolve({ spfDomains: 'spfDomains' })).toEqual('spfDomains') }) }) describe('testing the totalMessages resolvers', () => { diff --git a/api-js/src/dmarc-summaries/objects/__tests__/dmarc-summary-connection.test.js b/api/src/dmarc-summaries/objects/__tests__/dmarc-summary-connection.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/dmarc-summary-connection.test.js rename to api/src/dmarc-summaries/objects/__tests__/dmarc-summary-connection.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js b/api/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js similarity index 86% rename from api-js/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js rename to api/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js index cc437758c9..ed0b50fcba 100644 --- a/api-js/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js +++ b/api/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js @@ -16,7 +16,7 @@ describe('testing the period gql object', () => { const demoType = dmarcSummaryType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a domain field', () => { const demoType = dmarcSummaryType.getFields() @@ -40,9 +40,7 @@ describe('testing the period gql object', () => { const demoType = dmarcSummaryType.getFields() expect(demoType).toHaveProperty('categoryPercentages') - expect(demoType.categoryPercentages.type).toMatchObject( - categoryPercentagesType, - ) + expect(demoType.categoryPercentages.type).toMatchObject(categoryPercentagesType) }) it('has a categoryTotals field', () => { const demoType = dmarcSummaryType.getFields() @@ -62,9 +60,7 @@ describe('testing the period gql object', () => { it('returns the resolved value', () => { const demoType = dmarcSummaryType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('dmarcSummary', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('dmarcSummary', 1)) }) }) describe('testing the domain resolver', () => { @@ -108,22 +104,14 @@ describe('testing the period gql object', () => { it('returns the resolved value', () => { const demoType = dmarcSummaryType.getFields() - expect( - demoType.month.resolve( - { startDate: 'thirtyDays' }, - {}, - { moment: mockedMoment }, - ), - ).toEqual('january') + expect(demoType.month.resolve({ startDate: 'thirtyDays' }, {}, { moment: mockedMoment })).toEqual('january') }) }) describe('startDate is not set to thirty days', () => { it('returns the resolved value', () => { const demoType = dmarcSummaryType.getFields() - expect( - demoType.month.resolve({ startDate: '2021-01-01' }, {}, { moment }), - ).toEqual('january') + expect(demoType.month.resolve({ startDate: '2021-01-01' }, {}, { moment })).toEqual('january') }) }) }) @@ -140,22 +128,14 @@ describe('testing the period gql object', () => { it('returns the resolved value', () => { const demoType = dmarcSummaryType.getFields() - expect( - demoType.year.resolve( - { startDate: 'thirtyDays' }, - {}, - { moment: mockedMoment }, - ), - ).toEqual('2020') + expect(demoType.year.resolve({ startDate: 'thirtyDays' }, {}, { moment: mockedMoment })).toEqual('2020') }) }) describe('start date is not set to thirty days', () => { it('returns the resolved value', () => { const demoType = dmarcSummaryType.getFields() - expect( - demoType.year.resolve({ startDate: '2020-01-01' }, {}, { moment }), - ).toEqual('2020') + expect(demoType.year.resolve({ startDate: '2020-01-01' }, {}, { moment })).toEqual('2020') }) }) }) diff --git a/api-js/src/dmarc-summaries/objects/__tests__/full-pass-table-connection.test.js b/api/src/dmarc-summaries/objects/__tests__/full-pass-table-connection.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/full-pass-table-connection.test.js rename to api/src/dmarc-summaries/objects/__tests__/full-pass-table-connection.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js b/api/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js similarity index 82% rename from api-js/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js rename to api/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js index 1f03b5b728..c8ba45c2e0 100644 --- a/api-js/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js +++ b/api/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js @@ -9,7 +9,7 @@ describe('given the fullPassTable gql object', () => { const demoType = fullPassTableType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a dkimDomains field', () => { const demoType = fullPassTableType.getFields() @@ -66,54 +66,42 @@ describe('given the fullPassTable gql object', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('fullPass', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('fullPass', 1)) }) }) describe('testing the dkimDomains resolver', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect( - demoType.dkimDomains.resolve({ dkimDomains: 'dkimDomains' }), - ).toEqual('dkimDomains') + expect(demoType.dkimDomains.resolve({ dkimDomains: 'dkimDomains' })).toEqual('dkimDomains') }) }) describe('testing the dkimSelectors resolvers', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect( - demoType.dkimSelectors.resolve({ dkimSelectors: 'dkimSelectors' }), - ).toEqual('dkimSelectors') + expect(demoType.dkimSelectors.resolve({ dkimSelectors: 'dkimSelectors' })).toEqual('dkimSelectors') }) }) describe('testing the dnsHost resolver', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual( - 'dnsHost', - ) + expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual('dnsHost') }) }) describe('testing the envelopeFrom resolver', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect( - demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' }), - ).toEqual('envelopeFrom') + expect(demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' })).toEqual('envelopeFrom') }) }) describe('testing the headerFrom resolver', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect( - demoType.headerFrom.resolve({ headerFrom: 'headerFrom' }), - ).toEqual('headerFrom') + expect(demoType.headerFrom.resolve({ headerFrom: 'headerFrom' })).toEqual('headerFrom') }) }) describe('testing the sourceIpAddress resolver', () => { @@ -131,18 +119,14 @@ describe('given the fullPassTable gql object', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect( - demoType.spfDomains.resolve({ spfDomains: 'spfDomains' }), - ).toEqual('spfDomains') + expect(demoType.spfDomains.resolve({ spfDomains: 'spfDomains' })).toEqual('spfDomains') }) }) describe('testing the totalMessages resolver', () => { it('returns the resolved value', () => { const demoType = fullPassTableType.getFields() - expect(demoType.totalMessages.resolve({ totalMessages: 10 })).toEqual( - 10, - ) + expect(demoType.totalMessages.resolve({ totalMessages: 10 })).toEqual(10) }) }) }) diff --git a/api-js/src/dmarc-summaries/objects/__tests__/spf-failure-table-connection.test.js b/api/src/dmarc-summaries/objects/__tests__/spf-failure-table-connection.test.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/__tests__/spf-failure-table-connection.test.js rename to api/src/dmarc-summaries/objects/__tests__/spf-failure-table-connection.test.js diff --git a/api-js/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js b/api/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js similarity index 82% rename from api-js/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js rename to api/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js index f44a67b3f2..c35d341b5b 100644 --- a/api-js/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js +++ b/api/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js @@ -1,10 +1,4 @@ -import { - GraphQLID, - GraphQLInt, - GraphQLString, - GraphQLBoolean, - GraphQLNonNull, -} from 'graphql' +import { GraphQLID, GraphQLInt, GraphQLString, GraphQLBoolean, GraphQLNonNull } from 'graphql' import { toGlobalId } from 'graphql-relay' import { spfFailureTableType } from '../spf-failure-table' @@ -16,7 +10,7 @@ describe('given spfFailureTable gql object', () => { const demoType = spfFailureTableType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a dnsHost field', () => { const demoType = spfFailureTableType.getFields() @@ -84,53 +78,43 @@ describe('given spfFailureTable gql object', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('spfFail', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('spfFail', 1)) }) }) describe('testing the dnsHost resolver', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual( - 'dnsHost', - ) + expect(demoType.dnsHost.resolve({ dnsHost: 'dnsHost' })).toEqual('dnsHost') }) }) describe('testing the envelopeFrom resolver', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect( - demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' }), - ).toEqual('envelopeFrom') + expect(demoType.envelopeFrom.resolve({ envelopeFrom: 'envelopeFrom' })).toEqual('envelopeFrom') }) }) describe('testing the guidance resolver', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect(demoType.guidance.resolve({ guidance: 'guidance' })).toEqual( - 'guidance', - ) + expect(demoType.guidance.resolve({ guidance: 'guidance' })).toEqual('guidance') }) }) describe('testing the guidanceTag resolver', () => { describe('guidance is not null', () => { it('returns resolved value', async () => { const demoType = spfFailureTableType.getFields() - + const expectedResult = { - _id: 'aggregateGuidanceTags/agg1', + _id: 'guidanceTags/agg1', _key: 'agg1', _rev: 'rev', _type: 'guidanceTag', guidance: 'cool guidance for issue', id: 'agg1', - refLinksGuide: [ - { description: 'Link Description', ref_link: 'www.link.ca' }, - ], + refLinksGuide: [{ description: 'Link Description', ref_link: 'www.link.ca' }], refLinksTechnical: [ { description: 'Tech link description', @@ -140,16 +124,14 @@ describe('given spfFailureTable gql object', () => { tagId: 'agg1', tagName: 'cool-tag-name', } - + expect( await demoType.guidanceTag.resolve( { guidance: 'agg1' }, {}, { - loaders: { - loadAggregateGuidanceTagByTagId: { - load: jest.fn().mockReturnValue(expectedResult), - }, + dataSources: { + guidanceTag: { byTagId: jest.fn().mockReturnValue([expectedResult]) }, }, }, ), @@ -159,18 +141,16 @@ describe('given spfFailureTable gql object', () => { describe('guidance is null', () => { it('returns an empty obj', async () => { const demoType = spfFailureTableType.getFields() - + const expectedResult = {} - + expect( await demoType.guidanceTag.resolve( { guidance: null }, {}, { - loaders: { - loadAggregateGuidanceTagByTagId: { - load: jest.fn().mockReturnValue(expectedResult), - }, + dataSources: { + guidanceTag: { byTagId: jest.fn().mockReturnValue(expectedResult) }, }, }, ), @@ -182,9 +162,7 @@ describe('given spfFailureTable gql object', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect( - demoType.headerFrom.resolve({ headerFrom: 'headerFrom' }), - ).toEqual('headerFrom') + expect(demoType.headerFrom.resolve({ headerFrom: 'headerFrom' })).toEqual('headerFrom') }) }) describe('testing the sourceIpAddress resolver', () => { @@ -209,18 +187,14 @@ describe('given spfFailureTable gql object', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect( - demoType.spfDomains.resolve({ spfDomains: 'spfDomains' }), - ).toEqual('spfDomains') + expect(demoType.spfDomains.resolve({ spfDomains: 'spfDomains' })).toEqual('spfDomains') }) }) describe('testing the spfResults resolver', () => { it('returns the resolved value', () => { const demoType = spfFailureTableType.getFields() - expect( - demoType.spfResults.resolve({ spfResults: 'spfResults' }), - ).toEqual('spfResults') + expect(demoType.spfResults.resolve({ spfResults: 'spfResults' })).toEqual('spfResults') }) }) describe('testing the totalMessages resolver', () => { diff --git a/api-js/src/dmarc-summaries/objects/category-percentages.js b/api/src/dmarc-summaries/objects/category-percentages.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/category-percentages.js rename to api/src/dmarc-summaries/objects/category-percentages.js diff --git a/api-js/src/dmarc-summaries/objects/category-totals.js b/api/src/dmarc-summaries/objects/category-totals.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/category-totals.js rename to api/src/dmarc-summaries/objects/category-totals.js diff --git a/api-js/src/dmarc-summaries/objects/detail-tables.js b/api/src/dmarc-summaries/objects/detail-tables.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/detail-tables.js rename to api/src/dmarc-summaries/objects/detail-tables.js diff --git a/api-js/src/dmarc-summaries/objects/dkim-failure-table-connection.js b/api/src/dmarc-summaries/objects/dkim-failure-table-connection.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/dkim-failure-table-connection.js rename to api/src/dmarc-summaries/objects/dkim-failure-table-connection.js diff --git a/api-js/src/dmarc-summaries/objects/dkim-failure-table.js b/api/src/dmarc-summaries/objects/dkim-failure-table.js similarity index 85% rename from api-js/src/dmarc-summaries/objects/dkim-failure-table.js rename to api/src/dmarc-summaries/objects/dkim-failure-table.js index 950f312431..140022b472 100644 --- a/api-js/src/dmarc-summaries/objects/dkim-failure-table.js +++ b/api/src/dmarc-summaries/objects/dkim-failure-table.js @@ -1,17 +1,11 @@ -import { - GraphQLInt, - GraphQLObjectType, - GraphQLString, - GraphQLBoolean, -} from 'graphql' +import { GraphQLInt, GraphQLObjectType, GraphQLString, GraphQLBoolean } from 'graphql' import { globalIdField } from 'graphql-relay' import { guidanceTagType } from '../../guidance-tag/objects' export const dkimFailureTableType = new GraphQLObjectType({ name: 'DkimFailureTable', - description: - 'This table contains the data fields for senders who are in the DKIM fail category.', + description: 'This table contains the data fields for senders who are in the DKIM fail category.', fields: () => ({ id: globalIdField('dkimFail'), dkimAligned: { @@ -55,13 +49,10 @@ export const dkimFailureTableType = new GraphQLObjectType({ guidanceTag: { type: guidanceTagType, description: 'Guidance for any issues that were found from the report.', - resolve: async ( - { guidance }, - _args, - { loaders: { loadAggregateGuidanceTagByTagId } }, - ) => { + resolve: async ({ guidance }, _args, { dataSources: { guidanceTag } }) => { if (guidance) { - return await loadAggregateGuidanceTagByTagId.load(guidance) + const guidanceTags = await guidanceTag.byTagId({ tags: [guidance] }) + return guidanceTags[0] } return {} }, diff --git a/api-js/src/dmarc-summaries/objects/dmarc-failure-table-connection.js b/api/src/dmarc-summaries/objects/dmarc-failure-table-connection.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/dmarc-failure-table-connection.js rename to api/src/dmarc-summaries/objects/dmarc-failure-table-connection.js diff --git a/api-js/src/dmarc-summaries/objects/dmarc-failure-table.js b/api/src/dmarc-summaries/objects/dmarc-failure-table.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/dmarc-failure-table.js rename to api/src/dmarc-summaries/objects/dmarc-failure-table.js diff --git a/api-js/src/dmarc-summaries/objects/dmarc-summary-connection.js b/api/src/dmarc-summaries/objects/dmarc-summary-connection.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/dmarc-summary-connection.js rename to api/src/dmarc-summaries/objects/dmarc-summary-connection.js diff --git a/api-js/src/dmarc-summaries/objects/dmarc-summary.js b/api/src/dmarc-summaries/objects/dmarc-summary.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/dmarc-summary.js rename to api/src/dmarc-summaries/objects/dmarc-summary.js diff --git a/api-js/src/dmarc-summaries/objects/full-pass-table-connection.js b/api/src/dmarc-summaries/objects/full-pass-table-connection.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/full-pass-table-connection.js rename to api/src/dmarc-summaries/objects/full-pass-table-connection.js diff --git a/api-js/src/dmarc-summaries/objects/full-pass-table.js b/api/src/dmarc-summaries/objects/full-pass-table.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/full-pass-table.js rename to api/src/dmarc-summaries/objects/full-pass-table.js diff --git a/api-js/src/dmarc-summaries/objects/index.js b/api/src/dmarc-summaries/objects/index.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/index.js rename to api/src/dmarc-summaries/objects/index.js diff --git a/api-js/src/dmarc-summaries/objects/spf-failure-table-connection.js b/api/src/dmarc-summaries/objects/spf-failure-table-connection.js similarity index 100% rename from api-js/src/dmarc-summaries/objects/spf-failure-table-connection.js rename to api/src/dmarc-summaries/objects/spf-failure-table-connection.js diff --git a/api-js/src/dmarc-summaries/objects/spf-failure-table.js b/api/src/dmarc-summaries/objects/spf-failure-table.js similarity index 84% rename from api-js/src/dmarc-summaries/objects/spf-failure-table.js rename to api/src/dmarc-summaries/objects/spf-failure-table.js index 19e1e34cd3..f15cddf528 100644 --- a/api-js/src/dmarc-summaries/objects/spf-failure-table.js +++ b/api/src/dmarc-summaries/objects/spf-failure-table.js @@ -1,17 +1,11 @@ -import { - GraphQLInt, - GraphQLObjectType, - GraphQLString, - GraphQLBoolean, -} from 'graphql' +import { GraphQLInt, GraphQLObjectType, GraphQLString, GraphQLBoolean } from 'graphql' import { globalIdField } from 'graphql-relay' import { guidanceTagType } from '../../guidance-tag/objects' export const spfFailureTableType = new GraphQLObjectType({ name: 'SpfFailureTable', - description: - 'This table contains the data fields for senders who are in the SPF fail category.', + description: 'This table contains the data fields for senders who are in the SPF fail category.', fields: () => ({ id: globalIdField('spfFail'), dnsHost: { @@ -34,13 +28,10 @@ export const spfFailureTableType = new GraphQLObjectType({ guidanceTag: { type: guidanceTagType, description: 'Guidance for any issues that were found from the report.', - resolve: async ( - { guidance }, - _args, - { loaders: { loadAggregateGuidanceTagByTagId } }, - ) => { + resolve: async ({ guidance }, _args, { dataSources: { guidanceTag } }) => { if (guidance) { - return await loadAggregateGuidanceTagByTagId.load(guidance) + const guidanceTags = await guidanceTag.byTagId({ tags: [guidance] }) + return guidanceTags[0] } return {} }, diff --git a/api-js/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js b/api/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js similarity index 79% rename from api-js/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js rename to api/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js index 85a576c6ea..9c837bbe8a 100644 --- a/api-js/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js +++ b/api/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js @@ -1,32 +1,24 @@ import moment from 'moment' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { checkSuperAdmin, userRequired, verifiedRequired } from '../../../auth' import { loadDmarcSummaryConnectionsByUserId } from '../../loaders' import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the findMyDmarcSummaries query', () => { - let query, - drop, - truncate, - schema, - collections, - org, - i18n, - user, - domain, - dmarcSummary1 + let query, drop, truncate, schema, collections, org, i18n, user, domain, dmarcSummary1 beforeAll(async () => { i18n = setupI18n({ @@ -65,11 +57,15 @@ describe('given the findMyDmarcSummaries query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -77,7 +73,6 @@ describe('given the findMyDmarcSummaries query', () => { user = await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: true, }) @@ -156,9 +151,9 @@ describe('given the findMyDmarcSummaries query', () => { await drop() }) it('returns my dmarc summaries', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDmarcSummaries(first: 5, month: JANUARY, year: "2021") { edges { @@ -179,8 +174,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: user._key, @@ -198,17 +193,17 @@ describe('given the findMyDmarcSummaries query', () => { verifiedRequired: verifiedRequired({ i18n }), }, loaders: { - loadDmarcSummaryConnectionsByUserId: - loadDmarcSummaryConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - i18n, - loadStartDateFromPeriod: mockedStartDateLoader, - }), + loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + loadStartDateFromPeriod: mockedStartDateLoader, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -235,9 +230,7 @@ describe('given the findMyDmarcSummaries query', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved their dmarc summaries`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved their dmarc summaries`]) }) }) describe('given a unsuccessful query', () => { @@ -258,9 +251,9 @@ describe('given the findMyDmarcSummaries query', () => { }) describe('given the user is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDmarcSummaries(first: 5, month: JANUARY, year: "2021") { edges { @@ -281,8 +274,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: undefined, @@ -296,27 +289,24 @@ describe('given the findMyDmarcSummaries query', () => { }, }), verifiedRequired: verifiedRequired({ i18n }), + loginRequiredBool: true, }, loaders: { loadDmarcSummaryConnectionsByUserId: jest.fn(), }, }, - ) - const error = [ - new GraphQLError(`Authentication error. Please sign in.`), - ] + }) + const error = [new GraphQLError(`Authentication error. Please sign in.`)] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User attempted to access controlled content, but userKey was undefined.`, - ]) + expect(consoleOutput).toEqual([`User attempted to access controlled content, but userKey was undefined.`]) }) }) describe('given a loader error', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDmarcSummaries(first: 5, month: JANUARY, year: "2021") { edges { @@ -337,8 +327,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: user._key, @@ -348,24 +338,18 @@ describe('given the findMyDmarcSummaries query', () => { verifiedRequired: jest.fn(), }, loaders: { - loadDmarcSummaryConnectionsByUserId: - loadDmarcSummaryConnectionsByUserId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: user._key, - cleanseInput, - i18n, - loadStartDateFromPeriod: jest.fn(), - }), + loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId({ + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + loadStartDateFromPeriod: jest.fn(), + }), }, }, - ) - const error = [ - new GraphQLError( - `Unable to load DMARC summary data. Please try again.`, - ), - ] + }) + const error = [new GraphQLError(`Unable to load DMARC summary data. Please try again.`)] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -391,9 +375,9 @@ describe('given the findMyDmarcSummaries query', () => { }) describe('given the user is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDmarcSummaries(first: 5, month: JANUARY, year: "2021") { edges { @@ -414,8 +398,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: undefined, @@ -428,30 +412,25 @@ describe('given the findMyDmarcSummaries query', () => { load: jest.fn(), }, }), + loginRequiredBool: true, verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadDmarcSummaryConnectionsByUserId: jest.fn(), }, }, - ) - const error = [ - new GraphQLError( - "Erreur d'authentification. Veuillez vous connecter.", - ), - ] + }) + const error = [new GraphQLError("Erreur d'authentification. Veuillez vous connecter.")] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User attempted to access controlled content, but userKey was undefined.`, - ]) + expect(consoleOutput).toEqual([`User attempted to access controlled content, but userKey was undefined.`]) }) }) describe('given a loader error', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDmarcSummaries(first: 5, month: JANUARY, year: "2021") { edges { @@ -472,8 +451,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: user._key, @@ -483,24 +462,18 @@ describe('given the findMyDmarcSummaries query', () => { verifiedRequired: jest.fn(), }, loaders: { - loadDmarcSummaryConnectionsByUserId: - loadDmarcSummaryConnectionsByUserId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - userKey: user._key, - cleanseInput, - i18n, - loadStartDateFromPeriod: jest.fn(), - }), + loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId({ + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + loadStartDateFromPeriod: jest.fn(), + }), }, }, - ) - const error = [ - new GraphQLError( - 'Impossible de charger les données de synthèse DMARC. Veuillez réessayer.', - ), - ] + }) + const error = [new GraphQLError('Impossible de charger les données de synthèse DMARC. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/dmarc-summaries/queries/__tests__/get-all-verified-rua-domains.test.js b/api/src/dmarc-summaries/queries/__tests__/get-all-verified-rua-domains.test.js new file mode 100644 index 0000000000..6633df0663 --- /dev/null +++ b/api/src/dmarc-summaries/queries/__tests__/get-all-verified-rua-domains.test.js @@ -0,0 +1,35 @@ +import { getAllVerifiedRuaDomains } from '../get-all-verified-rua-domains' + +describe('getAllVerifiedRuaDomains', () => { + it('should return a JSON string', async () => { + const mockUserRequired = jest.fn().mockResolvedValue({ id: 'testUser' }) + const mockVerifiedRequired = jest.fn() + const mockCheckSuperAdmin = jest.fn().mockResolvedValue(true) + const mockSuperAdminRequired = jest.fn() + const mockLoadAllVerifiedRuaDomains = jest.fn().mockResolvedValue([{ key: 'testKey', domains: 'testDomains' }]) + + const result = await getAllVerifiedRuaDomains.resolve( + {}, + {}, + { + userKey: 'testUserKey', + auth: { + checkSuperAdmin: mockCheckSuperAdmin, + userRequired: mockUserRequired, + verifiedRequired: mockVerifiedRequired, + superAdminRequired: mockSuperAdminRequired, + }, + loaders: { + loadAllVerifiedRuaDomains: mockLoadAllVerifiedRuaDomains, + }, + }, + ) + + expect(result).toBe(JSON.stringify({ testKey: 'testDomains' }, null, 4)) + expect(mockUserRequired).toHaveBeenCalled() + expect(mockVerifiedRequired).toHaveBeenCalledWith({ user: { id: 'testUser' } }) + expect(mockCheckSuperAdmin).toHaveBeenCalled() + expect(mockSuperAdminRequired).toHaveBeenCalledWith({ user: { id: 'testUser' }, isSuperAdmin: true }) + expect(mockLoadAllVerifiedRuaDomains).toHaveBeenCalledWith({}) + }) +}) diff --git a/api-js/src/dmarc-summaries/queries/find-my-dmarc-summaries.js b/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js similarity index 78% rename from api-js/src/dmarc-summaries/queries/find-my-dmarc-summaries.js rename to api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js index 0360a250fe..cda2e995ae 100644 --- a/api-js/src/dmarc-summaries/queries/find-my-dmarc-summaries.js +++ b/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js @@ -1,4 +1,4 @@ -import { GraphQLNonNull, GraphQLString } from 'graphql' +import { GraphQLBoolean, GraphQLNonNull, GraphQLString } from 'graphql' import { connectionArgs } from 'graphql-relay' import { dmarcSummaryOrder } from '../inputs' @@ -15,17 +15,20 @@ export const findMyDmarcSummaries = { description: 'Ordering options for dmarc summaries connections', }, month: { - type: GraphQLNonNull(PeriodEnums), + type: new GraphQLNonNull(PeriodEnums), description: 'The month in which the returned data is relevant to.', }, year: { - type: GraphQLNonNull(Year), + type: new GraphQLNonNull(Year), description: 'The year in which the returned data is relevant to.', }, search: { type: GraphQLString, - description: - 'An optional string used to filter the results based on domains.', + description: 'An optional string used to filter the results based on domains.', + }, + isAffiliated: { + type: GraphQLBoolean, + description: 'Filter the results based on the users affiliation.', }, ...connectionArgs, }, @@ -39,7 +42,6 @@ export const findMyDmarcSummaries = { }, ) => { const user = await userRequired() - verifiedRequired({ user }) const isSuperAdmin = await checkSuperAdmin() diff --git a/api/src/dmarc-summaries/queries/get-all-verified-rua-domains.js b/api/src/dmarc-summaries/queries/get-all-verified-rua-domains.js new file mode 100644 index 0000000000..1107d43c83 --- /dev/null +++ b/api/src/dmarc-summaries/queries/get-all-verified-rua-domains.js @@ -0,0 +1,32 @@ +import { GraphQLString } from 'graphql' + +export const getAllVerifiedRuaDomains = { + type: GraphQLString, + description: 'JSON formatted output of all domains in verified organizations that send DMARC reports.', + resolve: async ( + _, + args, + { + userKey, + auth: { checkSuperAdmin, userRequired, verifiedRequired, superAdminRequired }, + loaders: { loadAllVerifiedRuaDomains }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + const ruaDomains = await loadAllVerifiedRuaDomains({ ...args }) + + console.info(`User ${userKey} successfully retrieved all domains with DMARC reports.`) + + const returnObj = {} + ruaDomains.forEach(({ key, domains }) => { + returnObj[key] = domains + }) + + return JSON.stringify(returnObj, null, 4) + }, +} diff --git a/api/src/dmarc-summaries/queries/index.js b/api/src/dmarc-summaries/queries/index.js new file mode 100644 index 0000000000..790f25d215 --- /dev/null +++ b/api/src/dmarc-summaries/queries/index.js @@ -0,0 +1,2 @@ +export * from './find-my-dmarc-summaries' +export * from './get-all-verified-rua-domains' diff --git a/api/src/dns-scan/data-source.js b/api/src/dns-scan/data-source.js new file mode 100644 index 0000000000..b7d2321215 --- /dev/null +++ b/api/src/dns-scan/data-source.js @@ -0,0 +1,8 @@ +import { loadDnsByKey, loadDnsConnectionsByDomainId } from './loaders' + +export class DnsScanDataSource { + constructor({ query, userKey, cleanseInput, i18n }) { + this.byKey = loadDnsByKey({ query, userKey, i18n }) + this.getConnectionsByDomainId = loadDnsConnectionsByDomainId({ query, userKey, cleanseInput, i18n }) + } +} diff --git a/api/src/dns-scan/index.js b/api/src/dns-scan/index.js new file mode 100644 index 0000000000..52de3be3af --- /dev/null +++ b/api/src/dns-scan/index.js @@ -0,0 +1,4 @@ +export * from './data-source' +export * from './inputs' +export * from './loaders' +export * from './objects' diff --git a/api/src/dns-scan/inputs/dns-order.js b/api/src/dns-scan/inputs/dns-order.js new file mode 100644 index 0000000000..879aadb374 --- /dev/null +++ b/api/src/dns-scan/inputs/dns-order.js @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' + +import { OrderDirection, DnsOrderField } from '../../enums' + +export const dnsOrder = new GraphQLInputObjectType({ + name: 'DNSOrder', + description: 'Ordering options for DNS connections.', + fields: () => ({ + field: { + type: new GraphQLNonNull(DnsOrderField), + description: 'The field to order DNS scans by.', + }, + direction: { + type: new GraphQLNonNull(OrderDirection), + description: 'The ordering direction.', + }, + }), +}) diff --git a/api/src/dns-scan/inputs/index.js b/api/src/dns-scan/inputs/index.js new file mode 100644 index 0000000000..88b0762fd2 --- /dev/null +++ b/api/src/dns-scan/inputs/index.js @@ -0,0 +1 @@ +export * from './dns-order' diff --git a/api/src/dns-scan/loaders/index.js b/api/src/dns-scan/loaders/index.js new file mode 100644 index 0000000000..9b63653ab8 --- /dev/null +++ b/api/src/dns-scan/loaders/index.js @@ -0,0 +1,2 @@ +export * from './load-dns-by-key' +export * from './load-dns-connections-by-domain-id' diff --git a/api/src/dns-scan/loaders/load-dns-by-key.js b/api/src/dns-scan/loaders/load-dns-by-key.js new file mode 100644 index 0000000000..2d44ecc3d4 --- /dev/null +++ b/api/src/dns-scan/loaders/load-dns-by-key.js @@ -0,0 +1,38 @@ +import DataLoader from 'dataloader' +import {t} from '@lingui/macro' + +export const loadDnsByKey = ({query, userKey, i18n}) => + new DataLoader(async (keys) => { + let cursor + + try { + cursor = await query` + WITH dns + FOR dnsScan IN dns + FILTER dnsScan._key IN ${keys} + RETURN MERGE({ id: dnsScan._key, _type: "dns" }, dnsScan) + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} running loadDnsByKey: ${err}`, + ) + throw new Error( + i18n._(t`Unable to find DNS scan(s). Please try again.`), + ) + } + + const dnsMap = {} + try { + await cursor.forEach((dnsScan) => { + dnsMap[dnsScan._key] = dnsScan + }) + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} running loadDnsByKey: ${err}`, + ) + throw new Error( + i18n._(t`Unable to find DNS scan(s). Please try again.`), + ) + } + return keys.map((key) => dnsMap[key]) + }) diff --git a/api/src/dns-scan/loaders/load-dns-connections-by-domain-id.js b/api/src/dns-scan/loaders/load-dns-connections-by-domain-id.js new file mode 100644 index 0000000000..cf218b5795 --- /dev/null +++ b/api/src/dns-scan/loaders/load-dns-connections-by-domain-id.js @@ -0,0 +1,239 @@ +import { aql } from 'arangojs' +import { t } from '@lingui/macro' + +export const loadDnsConnectionsByDomainId = + ({ query, userKey, cleanseInput, i18n }) => + async ({ limit, domainId, startDate, endDate, after, before, offset, orderBy }) => { + if (limit === undefined) { + console.warn(`User: ${userKey} did not set \`limit\` argument for: loadDnsConnectionsByDomainId.`) + throw new Error(i18n._(t`You must provide a \`limit\` value to properly paginate the \`DNS\` connection.`)) + } + + if (limit <= 0 || limit > 100) { + console.warn(`User: ${userKey} set \`limit\` argument outside accepted range: loadDnsConnectionsByDomainId.`) + throw new Error( + i18n._( + t`You must provide a \`limit\` value in the range of 1-100 to properly paginate the \`DNS\` connection.`, + ), + ) + } + + const paginationMethodCount = [before, after, offset].reduce( + (paginationMethod, currentValue) => currentValue + (paginationMethod === undefined), + 0, + ) + + if (paginationMethodCount > 1) { + console.warn(`User: ${userKey} set multiple pagination methods for: loadDnsConnectionsByDomainId.`) + throw new Error( + i18n._( + t`You must provide at most one pagination method (\`before\`, \`after\`, \`offset\`) value to properly paginate the \`DNS\` connection.`, + ), + ) + } + + before = cleanseInput(before) + after = cleanseInput(after) + + const usingRelayExplicitly = !!(before || after) + + const resolveCursor = (cursor) => { + const cursorString = Buffer.from(cursor, 'base64').toString('utf8').split('|') + + return cursorString.reduce((acc, currentValue) => { + const [type, id] = currentValue.split('::') + acc.push({ type, id }) + return acc + }, []) + } + + let relayBeforeTemplate = aql`` + let relayAfterTemplate = aql`` + if (usingRelayExplicitly) { + const cursorList = resolveCursor(after || before) + + if (cursorList.length === 0 || cursorList > 2) { + // TODO: throw error + } + + if (cursorList.at(-1).type !== 'id') { + // id field should always be last property + // TODO: throw error + } + + const orderByDirectionArrow = + orderBy?.direction === 'DESC' ? aql`<` : orderBy?.direction === 'ASC' ? aql`>` : null + const reverseOrderByDirectionArrow = + orderBy?.direction === 'DESC' ? aql`>` : orderBy?.direction === 'ASC' ? aql`<` : null + + relayBeforeTemplate = aql`FILTER TO_NUMBER(dnsScan._key) < TO_NUMBER(${cursorList[0].id})` + relayAfterTemplate = aql`FILTER TO_NUMBER(dnsScan._key) > TO_NUMBER(${cursorList[0].id})` + + if (cursorList.length === 2) { + relayAfterTemplate = aql` + FILTER dnsScan.${cursorList[0].type} ${orderByDirectionArrow || aql`>`} ${cursorList[0].id} + OR (dnsScan.${cursorList[0].type} == ${cursorList[0].id} + AND TO_NUMBER(dnsScan._key) > TO_NUMBER(${cursorList[1].id})) + ` + + relayBeforeTemplate = aql` + FILTER dnsScan.${cursorList[0].type} ${reverseOrderByDirectionArrow || aql`<`} ${cursorList[0].id} + OR (dnsScan.${cursorList[0].type} == ${cursorList[0].id} + AND TO_NUMBER(dnsScan._key) < TO_NUMBER(${cursorList[1].id})) + ` + } + } + + const relayDirectionString = before ? aql`DESC` : aql`ASC` + + let sortTemplate + if (!orderBy) { + sortTemplate = aql`SORT TO_NUMBER(dnsScan._key) ${relayDirectionString}` + } else { + sortTemplate = aql`SORT dnsScan.${orderBy.field} ${orderBy.direction}, TO_NUMBER(dnsScan._key) ${relayDirectionString}` + } + + let startDateFilter = aql`` + if (typeof startDate !== 'undefined') { + startDateFilter = aql` + FILTER DATE_FORMAT(dnsScan.timestamp, '%yyyy-%mm-%dd') >= DATE_FORMAT(${startDate}, '%yyyy-%mm-%dd')` + } + + let endDateFilter = aql`` + if (typeof endDate !== 'undefined') { + endDateFilter = aql` + FILTER DATE_FORMAT(dnsScan.timestamp, '%yyyy-%mm-%dd') <= DATE_FORMAT(${endDate}, '%yyyy-%mm-%dd')` + } + + const removeExtraSliceTemplate = aql`SLICE(dnsScansPlusOne, 0, ${limit})` + const dnsScanQuery = aql` + WITH dns, domains + LET dnsScansPlusOne = ( + FOR dnsScan, e IN 1 OUTBOUND ${domainId} domainsDNS + ${startDateFilter} + ${endDateFilter} + ${before ? relayBeforeTemplate : relayAfterTemplate} + ${sortTemplate} + LIMIT ${limit + 1} + RETURN MERGE({ id: dnsScan._key, _type: "dnsScan" }, dnsScan) + ) + LET hasMoreRelayPage = LENGTH(dnsScansPlusOne) == ${limit} + 1 + LET hasReversePage = ${!usingRelayExplicitly} ? false : (LENGTH( + FOR dnsScan, e IN 1 OUTBOUND ${domainId} domainsDNS + ${startDateFilter} + ${endDateFilter} + ${before ? relayAfterTemplate : relayBeforeTemplate} + LIMIT 1 + RETURN true + ) > 0) ? true : false + LET totalCount = COUNT( + FOR dnsScan, e IN 1 OUTBOUND ${domainId} domainsDNS + ${startDateFilter} + ${endDateFilter} + RETURN true + ) + LET dnsScans = ${removeExtraSliceTemplate} + + RETURN { + "dnsScans": dnsScans, + "hasMoreRelayPage": hasMoreRelayPage, + "hasReversePage": hasReversePage, + "totalCount": totalCount + } + ` + + let dnsScanCursor + try { + dnsScanCursor = await query`${dnsScanQuery}` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to get cursor for DNS document with cursor '${ + after || before + }' for domain '${domainId}', error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load DNS scan(s). Please try again.`)) + } + + let dnsScanInfo + try { + dnsScanInfo = await dnsScanCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to get DNS information for ${domainId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load DNS scan(s). Please try again.`)) + } + + const dnsScans = dnsScanInfo.dnsScans + + if (dnsScans.length === 0) { + return { + edges: [], + totalCount: dnsScanInfo.totalCount, + pageInfo: { + hasPreviousPage: !usingRelayExplicitly + ? false + : after + ? dnsScanInfo.hasReversePage + : dnsScanInfo.hasMoreRelayPage, + hasNextPage: after || !usingRelayExplicitly ? dnsScanInfo.hasMoreRelayPage : dnsScanInfo.hasReversePage, + startCursor: null, + endCursor: null, + }, + } + } + + const toCursorString = (cursorObjects) => { + const cursorStringArray = cursorObjects.reduce((acc, cursorObject) => { + if (cursorObject.type === undefined || cursorObject.id === undefined) { + // TODO: throw error + } + acc.push(`${cursorObject.type}::${cursorObject.id}`) + return acc + }, []) + const cursorString = cursorStringArray.join('|') + return Buffer.from(cursorString, 'utf8').toString('base64') + } + + const edges = dnsScans.map((dnsScan) => { + let cursor + if (orderBy) { + cursor = toCursorString([ + { + type: orderBy.field, + id: dnsScan[orderBy.field], + }, + { + type: 'id', + id: dnsScan._key, + }, + ]) + } else { + cursor = toCursorString([ + { + type: 'id', + id: dnsScan._key, + }, + ]) + } + return { + cursor: cursor, + node: dnsScan, + } + }) + + return { + edges: edges, + totalCount: dnsScanInfo.totalCount, + pageInfo: { + hasPreviousPage: !usingRelayExplicitly + ? false + : after + ? dnsScanInfo.hasReversePage + : dnsScanInfo.hasMoreRelayPage, + hasNextPage: after || !usingRelayExplicitly ? dnsScanInfo.hasMoreRelayPage : dnsScanInfo.hasReversePage, + endCursor: edges.length > 0 ? edges.at(-1).cursor : null, + startCursor: edges.length > 0 ? edges[0].cursor : null, + }, + } + } diff --git a/api/src/dns-scan/objects/dkim-selector-result.js b/api/src/dns-scan/objects/dkim-selector-result.js new file mode 100644 index 0000000000..0a51b0a474 --- /dev/null +++ b/api/src/dns-scan/objects/dkim-selector-result.js @@ -0,0 +1,69 @@ +import { GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { guidanceTagType } from '../../guidance-tag' + +export const dkimSelectorResultType = new GraphQLObjectType({ + name: 'DKIMSelectorResult', + fields: () => ({ + selector: { + type: GraphQLString, + description: `The selector which was scanned.`, + resolve: async ({ selector }) => selector, + }, + status: { + type: GraphQLString, + description: `The compliance status for DKIM for the scanned domain.`, + resolve: async ({ status }) => status, + }, + record: { + type: GraphQLString, + description: `DKIM record retrieved during scan.`, + resolve: ({ record }) => record, + }, + keyLength: { + type: GraphQLString, + description: 'Size of the Public Key in bits.', + resolve: ({ keyLength }) => keyLength, + }, + keyType: { + type: GraphQLString, + description: 'Type of DKIM key used.', + resolve: ({ keyType }) => keyType, + }, + publicExponent: { + type: GraphQLInt, + description: 'The public exponent used for DKIM.', + resolve: ({ publicExponent }) => publicExponent, + }, + keyModulus: { + type: GraphQLString, + description: 'The key modulus used.', + resolve: ({ keyModulus }) => keyModulus, + }, + positiveTags: { + type: new GraphQLList(guidanceTagType), + description: `List of positive tags for the scanned domain from this scan.`, + resolve: async ({ positiveTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.dkimByTagId({ tags: positiveTags }) + }, + }, + neutralTags: { + type: new GraphQLList(guidanceTagType), + description: `List of neutral tags for the scanned domain from this scan.`, + resolve: async ({ neutralTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.dkimByTagId({ tags: neutralTags }) + }, + }, + negativeTags: { + type: new GraphQLList(guidanceTagType), + description: `List of negative tags for the scanned domain from this scan.`, + resolve: async ({ negativeTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.dkimByTagId({ tags: negativeTags }) + }, + }, + }), + description: `DomainKeys Identified Mail (DKIM) permits a person, role, or +organization that owns the signing domain to claim some +responsibility for a message by associating the domain with the +message. This can be an author's organization, an operational relay, +or one of their agents.`, +}) diff --git a/api/src/dns-scan/objects/dkim.js b/api/src/dns-scan/objects/dkim.js new file mode 100644 index 0000000000..ed5ab22d16 --- /dev/null +++ b/api/src/dns-scan/objects/dkim.js @@ -0,0 +1,54 @@ +import { GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { dkimSelectorResultType } from './dkim-selector-result' +import { guidanceTagType } from '../../guidance-tag' + +export const dkimType = new GraphQLObjectType({ + name: 'DKIM', + fields: () => ({ + status: { + type: GraphQLString, + description: `The compliance status for DKIM for the scanned domain.`, + resolve: async ({ status }) => status, + }, + positiveTags: { + type: new GraphQLList(guidanceTagType), + description: `List of positive tags for the scanned domain from this scan.`, + resolve: async ({ positiveTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: positiveTags }) + }, + }, + neutralTags: { + type: new GraphQLList(guidanceTagType), + description: `List of neutral tags for the scanned domain from this scan.`, + resolve: async ({ neutralTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: neutralTags }) + }, + }, + negativeTags: { + type: new GraphQLList(guidanceTagType), + description: `List of negative tags for the scanned domain from this scan.`, + resolve: async ({ negativeTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: negativeTags }) + }, + }, + selectors: { + type: new GraphQLList(dkimSelectorResultType), + description: 'Individual scans results for each DKIM selector.', + resolve: async ({ selectors }) => { + const selectorArray = [] + for (const selector in selectors) { + selectorArray.push({ + selector: selector, + ...selectors[selector], + }) + } + return selectorArray + }, + }, + }), + description: `DomainKeys Identified Mail (DKIM) permits a person, role, or +organization that owns the signing domain to claim some +responsibility for a message by associating the domain with the +message. This can be an author's organization, an operational relay, +or one of their agents.`, +}) diff --git a/api/src/dns-scan/objects/dmarc.js b/api/src/dns-scan/objects/dmarc.js new file mode 100644 index 0000000000..dcaf40eb53 --- /dev/null +++ b/api/src/dns-scan/objects/dmarc.js @@ -0,0 +1,66 @@ +import { GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { guidanceTagType } from '../../guidance-tag/objects' + +export const dmarcType = new GraphQLObjectType({ + name: 'DMARC', + fields: () => ({ + status: { + type: GraphQLString, + description: `The compliance status for DMARC for the scanned domain.`, + resolve: async ({ status }) => status, + }, + record: { + type: GraphQLString, + description: `DMARC record retrieved during scan.`, + resolve: ({ record }) => record, + }, + pPolicy: { + type: GraphQLString, + description: `The requested policy you wish mailbox providers to apply +when your email fails DMARC authentication and alignment checks. `, + resolve: ({ pPolicy }) => pPolicy, + }, + spPolicy: { + type: GraphQLString, + description: `This tag is used to indicate a requested policy for all +subdomains where mail is failing the DMARC authentication and alignment checks.`, + resolve: ({ spPolicy }) => spPolicy, + }, + pct: { + type: GraphQLInt, + description: `The percentage of messages to which the DMARC policy is to be applied.`, + resolve: ({ pct }) => pct, + }, + phase: { + type: GraphQLString, + description: `The current phase of the DMARC implementation.`, + resolve: ({ phase }) => phase, + }, + positiveTags: { + type: new GraphQLList(guidanceTagType), + description: `List of positive tags for the scanned domain from this scan.`, + resolve: async ({ positiveTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: positiveTags }) + }, + }, + neutralTags: { + type: new GraphQLList(guidanceTagType), + description: `List of neutral tags for the scanned domain from this scan.`, + resolve: async ({ neutralTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: neutralTags }) + }, + }, + negativeTags: { + type: new GraphQLList(guidanceTagType), + description: `List of negative tags for the scanned domain from this scan.`, + resolve: async ({ negativeTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: negativeTags }) + }, + }, + }), + description: `Domain-based Message Authentication, Reporting, and Conformance +(DMARC) is a scalable mechanism by which a mail-originating +organization can express domain-level policies and preferences for +message validation, disposition, and reporting, that a mail-receiving +organization can use to improve mail handling.`, +}) diff --git a/api/src/dns-scan/objects/dns-scan-connection.js b/api/src/dns-scan/objects/dns-scan-connection.js new file mode 100644 index 0000000000..a72e711349 --- /dev/null +++ b/api/src/dns-scan/objects/dns-scan-connection.js @@ -0,0 +1,16 @@ +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' + +import {dnsScanType} from './dns-scan' + +export const dnsScanConnection = connectionDefinitions({ + name: 'DNSScan', + nodeType: dnsScanType, + connectionFields: () => ({ + totalCount: { + type: GraphQLInt, + description: 'The total amount of DNS scans related to a given domain.', + resolve: ({totalCount}) => totalCount, + }, + }), +}) diff --git a/api/src/dns-scan/objects/dns-scan.js b/api/src/dns-scan/objects/dns-scan.js new file mode 100644 index 0000000000..898666d7f9 --- /dev/null +++ b/api/src/dns-scan/objects/dns-scan.js @@ -0,0 +1,82 @@ +import { GraphQLBoolean, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { globalIdField } from 'graphql-relay' +import { GraphQLDateTime } from 'graphql-scalars' + +import { nodeInterface } from '../../node' +import { dmarcType } from './dmarc' +import { spfType } from './spf' +import { dkimType } from './dkim' +import { mxRecordType } from './mx-record' + +export const dnsScanType = new GraphQLObjectType({ + name: 'DNSScan', + fields: () => ({ + id: globalIdField('dns'), + domain: { + type: GraphQLString, + description: `The domain the scan was ran on.`, + resolve: async ({ domain }) => domain, + }, + timestamp: { + type: GraphQLDateTime, + description: `The time when the scan was initiated.`, + resolve: ({ timestamp }) => new Date(timestamp), + }, + baseDomain: { + type: GraphQLString, + description: `String of the base domain the scan was run on.`, + }, + recordExists: { + type: GraphQLBoolean, + description: `Whether or not there are DNS records for the domain scanned.`, + }, + resolveChain: { + type: new GraphQLList(new GraphQLList(GraphQLString)), + description: `The chain CNAME/IP addresses for the domain.`, + }, + cnameRecord: { + type: GraphQLString, + description: `The CNAME for the domain (if it exists).`, + }, + mxRecords: { + type: mxRecordType, + description: `The MX records for the domain (if they exist).`, + }, + nsRecords: { + type: nsRecordType, + description: `The NS records for the domain.`, + }, + dmarc: { + type: dmarcType, + description: `The DMARC scan results for the domain.`, + }, + spf: { + type: spfType, + description: `The SPF scan results for the domain.`, + }, + dkim: { + type: dkimType, + description: `The SKIM scan results for the domain.`, + }, + }), + interfaces: [nodeInterface], + description: `Results of DKIM, DMARC, and SPF scans on the given domain.`, +}) + +export const nsRecordType = new GraphQLObjectType({ + name: 'NSRecord', + fields: () => ({ + hostnames: { + type: new GraphQLList(GraphQLString), + description: `Hostnames for the nameservers for the domain.`, + }, + warnings: { + type: new GraphQLList(GraphQLString), + description: `Additional warning info about the NS record.`, + }, + error: { + type: GraphQLString, + description: `Error message if the NS record could not be retrieved.`, + }, + }), +}) diff --git a/api/src/dns-scan/objects/index.js b/api/src/dns-scan/objects/index.js new file mode 100644 index 0000000000..3ff3084458 --- /dev/null +++ b/api/src/dns-scan/objects/index.js @@ -0,0 +1,7 @@ +export * from './dkim' +export * from './dkim-selector-result' +export * from './dmarc' +export * from './dns-scan' +export * from './dns-scan-connection' +export * from './mx-record' +export * from './spf' diff --git a/api/src/dns-scan/objects/mx-record.js b/api/src/dns-scan/objects/mx-record.js new file mode 100644 index 0000000000..f4a339f4f0 --- /dev/null +++ b/api/src/dns-scan/objects/mx-record.js @@ -0,0 +1,38 @@ +import { GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' + +export const mxHostType = new GraphQLObjectType({ + name: 'MXHost', + fields: () => ({ + preference: { + type: GraphQLInt, + description: `The preference (or priority) of the host.`, + }, + hostname: { + type: GraphQLString, + description: `The hostname of the given host.`, + }, + addresses: { + type: new GraphQLList(GraphQLString), + description: `The IP addresses for the given host.`, + }, + }), + description: `Hosts listed in the domain's MX record.`, +}) + +export const mxRecordType = new GraphQLObjectType({ + name: 'MXRecord', + fields: () => ({ + hosts: { + type: new GraphQLList(mxHostType), + description: `Hosts listed in the domain's MX record.`, + }, + warnings: { + type: new GraphQLList(GraphQLString), + description: `Additional warning info about the MX record.`, + }, + error: { + type: GraphQLString, + description: `Error message if the MX record could not be retrieved.`, + }, + }), +}) diff --git a/api/src/dns-scan/objects/spf.js b/api/src/dns-scan/objects/spf.js new file mode 100644 index 0000000000..9104d7aa14 --- /dev/null +++ b/api/src/dns-scan/objects/spf.js @@ -0,0 +1,56 @@ +import { GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { guidanceTagType } from '../../guidance-tag/objects' + +export const spfType = new GraphQLObjectType({ + name: 'SPF', + fields: () => ({ + status: { + type: GraphQLString, + description: `The compliance status for SPF for the scanned domain.`, + resolve: async ({ status }) => status, + }, + record: { + type: GraphQLString, + description: `SPF record retrieved during the scan of the given domain.`, + resolve: ({ record }) => record, + }, + lookups: { + type: GraphQLInt, + description: `The amount of DNS lookups.`, + resolve: ({ lookups }) => lookups, + }, + spfDefault: { + type: GraphQLString, + description: `Instruction of what a recipient should do if there is not a match to your SPF record.`, + resolve: ({ spfDefault }) => spfDefault, + }, + positiveTags: { + type: new GraphQLList(guidanceTagType), + description: `List of positive tags for the scanned domain from this scan.`, + resolve: async ({ positiveTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: positiveTags }) + }, + }, + neutralTags: { + type: new GraphQLList(guidanceTagType), + description: `List of neutral tags for the scanned domain from this scan.`, + resolve: async ({ neutralTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: neutralTags }) + }, + }, + negativeTags: { + type: new GraphQLList(guidanceTagType), + description: `List of negative tags for the scanned domain from this scan.`, + resolve: async ({ negativeTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: negativeTags }) + }, + }, + }), + description: `Email on the Internet can be forged in a number of ways. In +particular, existing protocols place no restriction on what a sending +host can use as the "MAIL FROM" of a message or the domain given on +the SMTP HELO/EHLO commands. Version 1 of the Sender Policy Framework (SPF) +protocol is where Administrative Management Domains (ADMDs) can explicitly +authorize the hosts that are allowed to use their domain names, and a +receiving host can check such authorization.`, +}) diff --git a/api/src/domain/helpers/__tests__/build-domain-filters.test.js b/api/src/domain/helpers/__tests__/build-domain-filters.test.js new file mode 100644 index 0000000000..6bc33450a2 --- /dev/null +++ b/api/src/domain/helpers/__tests__/build-domain-filters.test.js @@ -0,0 +1,267 @@ +import { buildDomainFilters } from '../build-domain-filters' + +// Helper to extract the query string and bind vars from an aql result, +// stripping the accumulated prefix so assertions stay readable. +function parse(result) { + return { + query: result.query, + bindVars: result.bindVars, + } +} + +// ─── Empty / no-op cases ──────────────────────────────────────────────────── + +describe('buildDomainFilters – empty / no-op', () => { + it('returns an empty aql fragment when filters is undefined', () => { + const result = buildDomainFilters({}) + expect(result.query).toBe('') + expect(result.bindVars).toEqual({}) + }) + + it('returns an empty aql fragment when filters is null', () => { + const result = buildDomainFilters({ filters: null }) + expect(result.query).toBe('') + expect(result.bindVars).toEqual({}) + }) + + it('returns an empty aql fragment when filters is an empty array', () => { + const result = buildDomainFilters({ filters: [] }) + expect(result.query).toBe('') + expect(result.bindVars).toEqual({}) + }) +}) + +// ─── Status filters ───────────────────────────────────────────────────────── + +describe('buildDomainFilters – status filters (==)', () => { + const STATUS_CASES = [ + ['dmarc-status', 'v.status.dmarc'], + ['dkim-status', 'v.status.dkim'], + ['https-status', 'v.status.https'], + ['spf-status', 'v.status.spf'], + ['ciphers-status', 'v.status.ciphers'], + ['curves-status', 'v.status.curves'], + ['hsts-status', 'v.status.hsts'], + ['policy-status', 'v.status.policy'], + ['protocols-status', 'v.status.protocols'], + ['certificates-status', 'v.status.certificates'], + ] + + test.each(STATUS_CASES)('%s maps to %s with == comparison', (filterCategory, aqlField) => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory, comparison: '==', filterValue: 'pass' }], + }), + ) + expect(query).toContain(`FILTER ${aqlField} ==`) + expect(Object.values(bindVars)).toContain('pass') + }) +}) + +describe('buildDomainFilters – status filters (!=)', () => { + it('uses != comparison operator for dmarc-status', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'dmarc-status', comparison: '!=', filterValue: 'fail' }], + }), + ) + expect(query).toContain('FILTER v.status.dmarc !=') + expect(Object.values(bindVars)).toContain('fail') + }) + + it('uses != comparison operator for https-status', () => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'https-status', comparison: '!=', filterValue: 'info' }], + }), + ) + expect(query).toContain('FILTER v.status.https !=') + }) +}) + +// ─── Tag filters – mapped fields ───────────────────────────────────────────── + +describe('buildDomainFilters – tags filter (mapped fields)', () => { + const TAG_CASES = [ + ['archived', 'v.archived', true], + ['nxdomain', 'v.rcode', 'NXDOMAIN'], + ['blocked', 'v.blocked', true], + ['wildcard-sibling', 'v.wildcardSibling', true], + ['wildcard-entry', 'v.wildcardEntry', true], + ['scan-pending', 'v.webScanPending', true], + ['has-entrust-certificate', 'v.hasEntrustCertificate', true], + ['cve-detected', 'v.cveDetected', true], + ['cvd-enrolled', 'v.cvdEnrollment.status', 'enrolled'], + ['cvd-pending', 'v.cvdEnrollment.status', 'pending'], + ] + + test.each(TAG_CASES)('tag value "%s" filters on field %s', (filterValue, aqlField) => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'tags', comparison: '==', filterValue }], + }), + ) + expect(query).toContain(`FILTER ${aqlField}`) + }) + + it('applies != comparison for a mapped tag (archived)', () => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'tags', comparison: '!=', filterValue: 'archived' }], + }), + ) + expect(query).toContain('FILTER v.archived !=') + }) +}) + +describe('buildDomainFilters – tags filter (unmapped / free-form)', () => { + it('falls back to POSITION(e.tags, ...) for an unknown tag value', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'tags', comparison: '==', filterValue: 'some-custom-tag' }], + }), + ) + expect(query).toContain('FILTER POSITION(e.tags,') + expect(query).toContain('== true') + expect(Object.values(bindVars)).toContain('some-custom-tag') + }) + + it('uses != with POSITION for an unknown tag with != comparison', () => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'tags', comparison: '!=', filterValue: 'another-tag' }], + }), + ) + expect(query).toContain('POSITION(e.tags,') + expect(query).toContain('!= true') + }) +}) + +// ─── Other filter categories ───────────────────────────────────────────────── + +describe('buildDomainFilters – asset-state filter', () => { + it('filters on e.assetState with == comparison', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'asset-state', comparison: '==', filterValue: 'approved' }], + }), + ) + expect(query).toContain('FILTER e.assetState ==') + expect(Object.values(bindVars)).toContain('approved') + }) + + it('filters on e.assetState with != comparison', () => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'asset-state', comparison: '!=', filterValue: 'hidden' }], + }), + ) + expect(query).toContain('FILTER e.assetState !=') + }) +}) + +describe('buildDomainFilters – guidance-tag filter', () => { + it('filters using POSITION(negativeTags, ...) with == comparison', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'guidance-tag', comparison: '==', filterValue: 'weak-cipher' }], + }), + ) + expect(query).toContain('FILTER POSITION(negativeTags,') + expect(query).toContain('== true') + expect(Object.values(bindVars)).toContain('weak-cipher') + }) + + it('filters using POSITION(negativeTags, ...) with != comparison', () => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'guidance-tag', comparison: '!=', filterValue: 'weak-cipher' }], + }), + ) + expect(query).toContain('FILTER POSITION(negativeTags,') + expect(query).toContain('!= true') + }) +}) + +describe('buildDomainFilters – dmarc-phase filter', () => { + it('filters on v.phase with == comparison', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'dmarc-phase', comparison: '==', filterValue: 'assess' }], + }), + ) + expect(query).toContain('FILTER v.phase ==') + expect(Object.values(bindVars)).toContain('assess') + }) + + it('filters on v.phase with != comparison', () => { + const { query } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'dmarc-phase', comparison: '!=', filterValue: 'deploy' }], + }), + ) + expect(query).toContain('FILTER v.phase !=') + }) +}) + +describe('buildDomainFilters – unknown filterCategory', () => { + it('silently skips an unrecognised filter category', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [{ filterCategory: 'totally-unknown', comparison: '==', filterValue: 'x' }], + }), + ) + // The accumulated fragment should be empty — nothing appended + expect(query).toBe('') + expect(bindVars).toEqual({}) + }) +}) + +// ─── Multiple filters combined ─────────────────────────────────────────────── + +describe('buildDomainFilters – multiple filters', () => { + it('accumulates all filters into a single AQL fragment', () => { + const { query, bindVars } = parse( + buildDomainFilters({ + filters: [ + { filterCategory: 'dmarc-status', comparison: '==', filterValue: 'pass' }, + { filterCategory: 'https-status', comparison: '!=', filterValue: 'fail' }, + { filterCategory: 'asset-state', comparison: '==', filterValue: 'approved' }, + ], + }), + ) + expect(query).toContain('FILTER v.status.dmarc ==') + expect(query).toContain('FILTER v.status.https !=') + expect(query).toContain('FILTER e.assetState ==') + expect(Object.values(bindVars)).toContain('pass') + expect(Object.values(bindVars)).toContain('fail') + expect(Object.values(bindVars)).toContain('approved') + }) + + it('combines a mapped tag filter with a status filter', () => { + const { query } = parse( + buildDomainFilters({ + filters: [ + { filterCategory: 'tags', comparison: '==', filterValue: 'blocked' }, + { filterCategory: 'spf-status', comparison: '==', filterValue: 'pass' }, + ], + }), + ) + expect(query).toContain('FILTER v.blocked') + expect(query).toContain('FILTER v.status.spf') + }) + + it('handles an unknown filter mixed with a valid one without losing the valid one', () => { + const { query } = parse( + buildDomainFilters({ + filters: [ + { filterCategory: 'dmarc-phase', comparison: '==', filterValue: 'assess' }, + { filterCategory: 'not-a-real-category', comparison: '==', filterValue: 'nope' }, + ], + }), + ) + expect(query).toContain('FILTER v.phase ==') + // The unknown category should not add anything extra + expect(query).not.toContain('nope') + }) +}) diff --git a/api/src/domain/helpers/build-domain-filters.js b/api/src/domain/helpers/build-domain-filters.js new file mode 100644 index 0000000000..e43039d971 --- /dev/null +++ b/api/src/domain/helpers/build-domain-filters.js @@ -0,0 +1,121 @@ +import { aql } from 'arangojs' + +// Maps filter categories to their AQL field paths +const STATUS_FILTER_MAP = { + 'dmarc-status': 'v.status.dmarc', + 'dkim-status': 'v.status.dkim', + 'https-status': 'v.status.https', + 'spf-status': 'v.status.spf', + 'ciphers-status': 'v.status.ciphers', + 'curves-status': 'v.status.curves', + 'hsts-status': 'v.status.hsts', + 'policy-status': 'v.status.policy', + 'protocols-status': 'v.status.protocols', + 'certificates-status': 'v.status.certificates', +} + +// Maps tag filter values to { field, value } pairs for direct field comparisons +const TAG_FIELD_MAP = { + archived: { field: 'v.archived', value: true }, + nxdomain: { field: 'v.rcode', value: 'NXDOMAIN' }, + blocked: { field: 'v.blocked', value: true }, + 'wildcard-sibling': { field: 'v.wildcardSibling', value: true }, + 'wildcard-entry': { field: 'v.wildcardEntry', value: true }, + 'scan-pending': { field: 'v.webScanPending', value: true }, + 'has-entrust-certificate': { field: 'v.hasEntrustCertificate', value: true }, + 'cve-detected': { field: 'v.cveDetected', value: true }, + 'cvd-enrolled': { field: 'v.cvdEnrollment.status', value: 'enrolled' }, + 'cvd-pending': { field: 'v.cvdEnrollment.status', value: 'pending' }, + 'cvd-deny': { field: 'v.cvdEnrollment.status', value: 'deny' }, +} + +function buildComparison(comparison) { + return comparison === '==' ? aql`==` : aql`!=` +} + +function buildStatusFilter(accumulated, field, comparison, filterValue) { + // AQL doesn't support dynamic field access, so we need a switch here. + // The field string is only ever sourced from STATUS_FILTER_MAP above, never user input. + switch (field) { + case 'v.status.dmarc': + return aql`${accumulated} FILTER v.status.dmarc ${comparison} ${filterValue}` + case 'v.status.dkim': + return aql`${accumulated} FILTER v.status.dkim ${comparison} ${filterValue}` + case 'v.status.https': + return aql`${accumulated} FILTER v.status.https ${comparison} ${filterValue}` + case 'v.status.spf': + return aql`${accumulated} FILTER v.status.spf ${comparison} ${filterValue}` + case 'v.status.ciphers': + return aql`${accumulated} FILTER v.status.ciphers ${comparison} ${filterValue}` + case 'v.status.curves': + return aql`${accumulated} FILTER v.status.curves ${comparison} ${filterValue}` + case 'v.status.hsts': + return aql`${accumulated} FILTER v.status.hsts ${comparison} ${filterValue}` + case 'v.status.policy': + return aql`${accumulated} FILTER v.status.policy ${comparison} ${filterValue}` + case 'v.status.protocols': + return aql`${accumulated} FILTER v.status.protocols ${comparison} ${filterValue}` + case 'v.status.certificates': + return aql`${accumulated} FILTER v.status.certificates ${comparison} ${filterValue}` + default: + return accumulated + } +} + +function buildTagFilter(accumulated, comparison, filterValue) { + const mapped = TAG_FIELD_MAP[filterValue] + + if (!mapped) { + return aql`${accumulated} FILTER POSITION(e.tags, ${filterValue}) ${comparison} true` + } + + // Same dynamic field problem — switch on the known field paths from TAG_FIELD_MAP + switch (mapped.field) { + case 'v.archived': + return aql`${accumulated} FILTER v.archived ${comparison} ${mapped.value}` + case 'v.rcode': + return aql`${accumulated} FILTER v.rcode ${comparison} ${mapped.value}` + case 'v.blocked': + return aql`${accumulated} FILTER v.blocked ${comparison} ${mapped.value}` + case 'v.wildcardSibling': + return aql`${accumulated} FILTER v.wildcardSibling ${comparison} ${mapped.value}` + case 'v.wildcardEntry': + return aql`${accumulated} FILTER v.wildcardEntry ${comparison} ${mapped.value}` + case 'v.webScanPending': + return aql`${accumulated} FILTER v.webScanPending ${comparison} ${mapped.value}` + case 'v.hasEntrustCertificate': + return aql`${accumulated} FILTER v.hasEntrustCertificate ${comparison} ${mapped.value}` + case 'v.cveDetected': + return aql`${accumulated} FILTER v.cveDetected ${comparison} ${mapped.value}` + case 'v.cvdEnrollment.status': + return aql`${accumulated} FILTER v.cvdEnrollment.status ${comparison} ${mapped.value}` + default: + return accumulated + } +} + +function buildSingleFilter(accumulated, { filterCategory, comparison, filterValue }) { + const cmp = buildComparison(comparison) + + if (filterCategory in STATUS_FILTER_MAP) { + return buildStatusFilter(accumulated, STATUS_FILTER_MAP[filterCategory], cmp, filterValue) + } + + switch (filterCategory) { + case 'tags': + return buildTagFilter(accumulated, cmp, filterValue) + case 'asset-state': + return aql`${accumulated} FILTER e.assetState ${cmp} ${filterValue}` + case 'guidance-tag': + return aql`${accumulated} FILTER POSITION(negativeTags, ${filterValue}) ${cmp} true` + case 'dmarc-phase': + return aql`${accumulated} FILTER v.phase ${cmp} ${filterValue}` + default: + return accumulated + } +} + +export function buildDomainFilters({ filters }) { + if (!filters?.length) return aql`` + return filters.reduce(buildSingleFilter, aql``) +} diff --git a/api/src/domain/helpers/index.js b/api/src/domain/helpers/index.js new file mode 100644 index 0000000000..2e6a27b84a --- /dev/null +++ b/api/src/domain/helpers/index.js @@ -0,0 +1 @@ +export * from './build-domain-filters' diff --git a/api-js/src/domain/index.js b/api/src/domain/index.js similarity index 100% rename from api-js/src/domain/index.js rename to api/src/domain/index.js diff --git a/api/src/domain/inputs/__tests__/domain-order.test.js b/api/src/domain/inputs/__tests__/domain-order.test.js new file mode 100644 index 0000000000..298e193e85 --- /dev/null +++ b/api/src/domain/inputs/__tests__/domain-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { domainOrder } from '../domain-order' +import { OrderDirection, DomainOrderField } from '../../../enums' + +describe('given the domainOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = domainOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = domainOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(DomainOrderField)) + }) + }) +}) diff --git a/api/src/domain/inputs/domain-filter.js b/api/src/domain/inputs/domain-filter.js new file mode 100644 index 0000000000..f69a4d4c61 --- /dev/null +++ b/api/src/domain/inputs/domain-filter.js @@ -0,0 +1,22 @@ +import { GraphQLInputObjectType } from 'graphql' +import { ComparisonEnums, DomainFilterCategory } from '../../enums' +import { FilterValueScalar } from '../../scalars/filter-value' + +export const domainFilter = new GraphQLInputObjectType({ + name: 'DomainFilter', + description: 'This object is used to provide filtering options when querying org-claimed domains.', + fields: () => ({ + filterCategory: { + type: DomainFilterCategory, + description: 'Category of filter to be applied.', + }, + comparison: { + type: ComparisonEnums, + description: 'First value equals or does not equal second value.', + }, + filterValue: { + type: FilterValueScalar, + description: 'Status type or tag label.', + }, + }), +}) diff --git a/api-js/src/domain/inputs/domain-order.js b/api/src/domain/inputs/domain-order.js similarity index 81% rename from api-js/src/domain/inputs/domain-order.js rename to api/src/domain/inputs/domain-order.js index e37afbebf2..c18a19b10e 100644 --- a/api-js/src/domain/inputs/domain-order.js +++ b/api/src/domain/inputs/domain-order.js @@ -7,11 +7,11 @@ export const domainOrder = new GraphQLInputObjectType({ description: 'Ordering options for domain connections.', fields: () => ({ field: { - type: GraphQLNonNull(DomainOrderField), + type: new GraphQLNonNull(DomainOrderField), description: 'The field to order domains by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api/src/domain/inputs/index.js b/api/src/domain/inputs/index.js new file mode 100644 index 0000000000..a1b014c4b5 --- /dev/null +++ b/api/src/domain/inputs/index.js @@ -0,0 +1,2 @@ +export * from './domain-filter' +export * from './domain-order' diff --git a/api-js/src/domain/loaders/__tests__/load-domain-by-domain.test.js b/api/src/domain/loaders/__tests__/load-domain-by-domain.test.js similarity index 84% rename from api-js/src/domain/loaders/__tests__/load-domain-by-domain.test.js rename to api/src/domain/loaders/__tests__/load-domain-by-domain.test.js index 6df7e52bf9..2e43e661e8 100644 --- a/api-js/src/domain/loaders/__tests__/load-domain-by-domain.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-by-domain.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadDomainByDomain } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -21,11 +22,15 @@ describe('given a loadDomainByDomain dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -96,9 +101,7 @@ describe('given a loadDomainByDomain dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadDomainByDomain({ query: mockedQuery, userKey: '1234', @@ -108,9 +111,7 @@ describe('given a loadDomainByDomain dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain. Please try again.')) } expect(consoleOutput).toEqual([ @@ -135,9 +136,7 @@ describe('given a loadDomainByDomain dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain. Please try again.')) } expect(consoleOutput).toEqual([ @@ -163,9 +162,7 @@ describe('given a loadDomainByDomain dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadDomainByDomain({ query: mockedQuery, userKey: '1234', @@ -175,9 +172,7 @@ describe('given a loadDomainByDomain dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Impossible de charger le domaine. Veuillez réessayer.'), - ) + expect(err).toEqual(new Error('Impossible de charger le domaine. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -202,9 +197,7 @@ describe('given a loadDomainByDomain dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Impossible de charger le domaine. Veuillez réessayer.'), - ) + expect(err).toEqual(new Error('Impossible de charger le domaine. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/domain/loaders/__tests__/load-domain-by-key.test.js b/api/src/domain/loaders/__tests__/load-domain-by-key.test.js similarity index 84% rename from api-js/src/domain/loaders/__tests__/load-domain-by-key.test.js rename to api/src/domain/loaders/__tests__/load-domain-by-key.test.js index c96211e080..761eca50ea 100644 --- a/api-js/src/domain/loaders/__tests__/load-domain-by-key.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-by-key.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadDomainByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,11 +24,15 @@ describe('given a loadDomainByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -100,9 +105,7 @@ describe('given a loadDomainByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadDomainByKey({ query: mockedQuery, userKey: '1234', @@ -112,9 +115,7 @@ describe('given a loadDomainByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain. Please try again.')) } expect(consoleOutput).toEqual([ @@ -139,9 +140,7 @@ describe('given a loadDomainByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain. Please try again.')) } expect(consoleOutput).toEqual([ @@ -167,9 +166,7 @@ describe('given a loadDomainByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadDomainByKey({ query: mockedQuery, userKey: '1234', @@ -179,9 +176,7 @@ describe('given a loadDomainByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Impossible de charger le domaine. Veuillez réessayer.'), - ) + expect(err).toEqual(new Error('Impossible de charger le domaine. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -206,9 +201,7 @@ describe('given a loadDomainByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Impossible de charger le domaine. Veuillez réessayer.'), - ) + expect(err).toEqual(new Error('Impossible de charger le domaine. Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js b/api/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js similarity index 76% rename from api-js/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js rename to api/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js index fb7d809f88..b6551a8595 100644 --- a/api-js/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js @@ -1,18 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadDomainConnectionsByOrgId, loadDomainByKey } from '../index' import { toGlobalId } from 'graphql-relay' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the load domain connection using org id function', () => { - let query, drop, truncate, collections, user, org, domain, domainTwo, i18n + let query, drop, truncate, collections, user, org, domain, domainTwo, i18n, language const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -29,18 +30,22 @@ describe('given the load domain connection using org id function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) + language = 'en' }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -76,7 +81,7 @@ describe('given the load domain connection using org id function', () => { domain = await collections.domains.save({ domain: 'test.domain.gc.ca', lastRan: '2021-01-02 12:12:12.000000', - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -84,15 +89,18 @@ describe('given the load domain connection using org id function', () => { spf: 'pass', ssl: 'pass', }, + archived: false, }) await collections.claims.save({ _from: org._id, _to: domain._id, + tags: [], + assetState: 'approved', }) domainTwo = await collections.domains.save({ domain: 'test.domain.canada.ca', lastRan: '2021-01-01 12:12:12.000000', - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'fail', dmarc: 'fail', @@ -100,10 +108,13 @@ describe('given the load domain connection using org id function', () => { spf: 'fail', ssl: 'fail', }, + archived: false, }) await collections.claims.save({ _from: org._id, _to: domainTwo._id, + tags: [], + assetState: 'approved', }) }) afterEach(async () => { @@ -117,14 +128,13 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -144,6 +154,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -163,15 +175,14 @@ describe('given the load domain connection using org id function', () => { it('returns a domain', async () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, + language, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -191,6 +202,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -210,15 +223,14 @@ describe('given the load domain connection using org id function', () => { it('returns a domain', async () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, + language, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -237,6 +249,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -257,14 +271,13 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -283,6 +296,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -312,7 +327,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) @@ -332,6 +349,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomain._key), node: { ...expectedDomain, + claimTags: [], + assetState: 'approved', }, }, ], @@ -353,7 +372,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const connectionArgs = { @@ -387,6 +408,8 @@ describe('given the load domain connection using org id function', () => { await collections.claims.save({ _from: org._id, _to: domainThree._id, + tags: [], + assetState: 'approved', }) await collections.ownership.save({ _from: org._id, @@ -398,13 +421,13 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequiredBool: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainThree._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainThree._key]) expectedDomains[0].id = expectedDomains[0]._key @@ -423,6 +446,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -443,15 +468,13 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - domainThree._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key, domainThree._key]) const connectionArgs = { first: 5, @@ -468,18 +491,24 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, { cursor: toGlobalId('domain', expectedDomains[2]._key), node: { ...expectedDomains[2], + claimTags: [], + assetState: 'approved', }, }, ], @@ -502,10 +531,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -521,7 +547,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -534,6 +562,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -552,10 +582,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -571,7 +598,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -585,110 +614,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on LAST_RAN', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'last-ran', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'last-ran', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -709,10 +636,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -728,7 +652,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -742,6 +668,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -760,10 +688,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -779,7 +704,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -793,6 +720,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -813,10 +742,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -832,7 +758,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -846,6 +774,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -864,10 +794,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -883,7 +810,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -897,6 +826,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -917,10 +848,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -936,7 +864,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -950,6 +880,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -968,10 +900,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -987,7 +916,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1001,6 +932,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1021,10 +954,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1040,7 +970,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1054,6 +986,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1072,10 +1006,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1091,7 +1022,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1105,110 +1038,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on SSL_STATUS', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'ssl-status', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'ssl-status', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1231,10 +1062,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1250,7 +1078,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1264,6 +1094,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1282,10 +1114,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1301,7 +1130,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1315,110 +1146,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on LAST_RAN', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'last-ran', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'last-ran', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1439,10 +1168,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1458,7 +1184,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1472,6 +1200,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1490,10 +1220,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1509,7 +1236,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1523,6 +1252,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1543,10 +1274,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1562,7 +1290,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1576,6 +1306,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1594,10 +1326,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1613,7 +1342,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1627,6 +1358,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1647,10 +1380,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1666,7 +1396,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1680,6 +1412,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1698,10 +1432,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1717,7 +1448,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1731,6 +1464,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1751,10 +1486,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1770,7 +1502,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1784,6 +1518,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1802,10 +1538,7 @@ describe('given the load domain connection using org id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1821,7 +1554,9 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ orgId: org._id, @@ -1835,110 +1570,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on SSL_STATUS', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'ssl-status', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'ssl-status', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ - orgId: org._id, - ownership: false, - ...connectionArgs, - }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1980,6 +1613,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2008,6 +1642,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2022,9 +1657,7 @@ describe('given the load domain connection using org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`, - ), + new Error(`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`), ) } @@ -2040,6 +1673,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2052,11 +1686,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` on the \`Domain\` connection cannot be less than zero.`)) } expect(consoleOutput).toEqual([ @@ -2070,6 +1700,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2082,11 +1713,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` on the \`Domain\` connection cannot be less than zero.`)) } expect(consoleOutput).toEqual([ @@ -2102,6 +1729,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2132,6 +1760,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2160,13 +1789,12 @@ describe('given the load domain connection using org id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2180,11 +1808,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -2196,13 +1820,12 @@ describe('given the load domain connection using org id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2216,11 +1839,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -2235,14 +1854,13 @@ describe('given the load domain connection using org id function', () => { describe('given a database error', () => { describe('when gathering domain keys that are claimed by orgs that the user has affiliations to', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2255,9 +1873,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -2280,6 +1896,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2292,9 +1909,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -2326,6 +1941,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2354,6 +1970,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2368,9 +1985,7 @@ describe('given the load domain connection using org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.", - ), + new Error("Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté."), ) } @@ -2386,6 +2001,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2398,11 +2014,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `Domain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`first` sur la connexion `Domain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -2416,6 +2028,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2428,11 +2041,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `Domain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `Domain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -2448,6 +2057,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2478,6 +2088,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2506,13 +2117,12 @@ describe('given the load domain connection using org id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2527,9 +2137,7 @@ describe('given the load domain connection using org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -2542,13 +2150,12 @@ describe('given the load domain connection using org id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2563,9 +2170,7 @@ describe('given the load domain connection using org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -2581,14 +2186,13 @@ describe('given the load domain connection using org id function', () => { describe('given a database error', () => { describe('when gathering domain keys that are claimed by orgs that the user has affiliations to', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2601,11 +2205,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -2628,6 +2228,7 @@ describe('given the load domain connection using org id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2640,11 +2241,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js b/api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js similarity index 76% rename from api-js/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js rename to api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js index a5f8153c07..0286f4ae01 100644 --- a/api-js/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js @@ -1,13 +1,14 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadDomainConnectionsByUserId, loadDomainByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -29,18 +30,21 @@ describe('given the load domain connections by user id function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -76,7 +80,7 @@ describe('given the load domain connections by user id function', () => { domainOne = await collections.domains.save({ domain: 'test1.gc.ca', lastRan: '2021-01-01 12:12:12.000000', - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'fail', dmarc: 'fail', @@ -88,7 +92,7 @@ describe('given the load domain connections by user id function', () => { domainTwo = await collections.domains.save({ domain: 'test2.gc.ca', lastRan: '2021-01-02 12:12:12.000000', - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -119,13 +123,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -163,13 +165,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -207,13 +207,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -250,13 +248,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -302,6 +298,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) @@ -341,7 +338,7 @@ describe('given the load domain connections by user id function', () => { domainThree = await collections.domains.save({ domain: 'test3.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], }) await collections.claims.save({ _to: domainThree._id, @@ -358,12 +355,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequiredBool: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainThree._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainThree._key]) const connectionArgs = { first: 1, @@ -398,14 +394,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - domainThree._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key, domainThree._key]) const connectionArgs = { first: 3, @@ -453,10 +446,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -473,6 +463,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -500,10 +491,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -520,102 +508,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on LAST_RAN', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'last-ran', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'last-ran', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -645,10 +538,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -665,6 +555,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -692,10 +583,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -712,6 +600,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -741,10 +630,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -761,6 +647,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -788,10 +675,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -808,6 +692,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -837,10 +722,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -857,6 +739,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -884,10 +767,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -904,6 +784,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -933,10 +814,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -953,6 +831,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -980,10 +859,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1000,102 +876,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on SSL_STATUS', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'ssl-status', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: false, - hasPreviousPage: true, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - after: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'ssl-status', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1127,10 +908,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1147,6 +925,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1174,10 +953,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1194,102 +970,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - }) - describe('ordering on LAST_RAN', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'last-ran', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } - - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'last-ran', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1319,10 +1000,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1339,6 +1017,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1366,10 +1045,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1386,6 +1062,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1415,10 +1092,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1435,6 +1109,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1462,10 +1137,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1482,6 +1154,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1511,10 +1184,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1531,6 +1201,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1558,10 +1229,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1578,6 +1246,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1607,10 +1276,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1627,6 +1293,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1654,10 +1321,7 @@ describe('given the load domain connections by user id function', () => { describe('order direction is DESC', () => { it('returns domains in order', async () => { const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1674,6 +1338,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const domains = await connectionLoader({ ...connectionArgs }) @@ -1699,123 +1364,73 @@ describe('given the load domain connections by user id function', () => { }) }) }) - describe('ordering on SSL_STATUS', () => { - describe('order direction is ASC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) - - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key - - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[1]._key), - orderBy: { - field: 'ssl-status', - direction: 'ASC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) - - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[0]._key), - node: { - ...expectedDomains[0], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[0]._key), - endCursor: toGlobalId('domain', expectedDomains[0]._key), - }, - } + }) + }) + describe('isSuperAdmin is set to true', () => { + it('returns a domain', async () => { + const connectionLoader = loadDomainConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + }) - expect(domains).toEqual(expectedStructure) - }) - }) - describe('order direction is DESC', () => { - it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const connectionArgs = { + first: 10, + isSuperAdmin: true, + } + const domains = await connectionLoader({ ...connectionArgs }) - expectedDomains[0].id = expectedDomains[0]._key - expectedDomains[1].id = expectedDomains[1]._key + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) - const connectionArgs = { - first: 1, - before: toGlobalId('domain', expectedDomains[0]._key), - orderBy: { - field: 'ssl-status', - direction: 'DESC', - }, - } - const connectionLoader = loadDomainConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - }) - const domains = await connectionLoader({ ...connectionArgs }) + expectedDomains[0].id = expectedDomains[0]._key + expectedDomains[1].id = expectedDomains[1]._key - const expectedStructure = { - edges: [ - { - cursor: toGlobalId('domain', expectedDomains[1]._key), - node: { - ...expectedDomains[1], - }, - }, - ], - totalCount: 2, - pageInfo: { - hasNextPage: true, - hasPreviousPage: false, - startCursor: toGlobalId('domain', expectedDomains[1]._key), - endCursor: toGlobalId('domain', expectedDomains[1]._key), - }, - } + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('domain', expectedDomains[0]._key), + node: { + ...expectedDomains[0], + }, + }, + { + cursor: toGlobalId('domain', expectedDomains[1]._key), + node: { + ...expectedDomains[1], + }, + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('domain', expectedDomains[0]._key), + endCursor: toGlobalId('domain', expectedDomains[1]._key), + }, + } - expect(domains).toEqual(expectedStructure) - }) - }) - }) + expect(domains).toEqual(expectedStructure) }) }) - describe('isSuperAdmin is set to true', () => { + describe('isAffiliated is set to true', () => { it('returns a domain', async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const connectionArgs = { first: 10, - isSuperAdmin: true, + isAffiliated: true, } const domains = await connectionLoader({ ...connectionArgs }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1856,6 +1471,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, }) const connectionArgs = { @@ -1900,6 +1516,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -1927,6 +1544,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -1940,9 +1558,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`, - ), + new Error(`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`), ) } @@ -1958,6 +1574,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -1987,6 +1604,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2018,6 +1636,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2029,11 +1648,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` on the \`Domain\` connection cannot be less than zero.`)) } expect(consoleOutput).toEqual([ @@ -2047,6 +1662,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2058,11 +1674,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` on the \`Domain\` connection cannot be less than zero.`)) } expect(consoleOutput).toEqual([ @@ -2074,13 +1686,12 @@ describe('given the load domain connections by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2093,11 +1704,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -2109,13 +1716,12 @@ describe('given the load domain connections by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2128,11 +1734,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -2147,16 +1749,13 @@ describe('given the load domain connections by user id function', () => { describe('given a database error', () => { describe('while querying for domain information', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query domains. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query domains. Please try again.')) const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2168,9 +1767,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to query domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to query domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -2193,6 +1790,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2204,9 +1802,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -2238,6 +1834,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2265,6 +1862,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2278,9 +1876,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.", - ), + new Error("Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté."), ) } @@ -2296,6 +1892,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2325,6 +1922,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2356,6 +1954,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2367,11 +1966,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `Domain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`first` sur la connexion `Domain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -2385,6 +1980,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2396,11 +1992,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `Domain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `Domain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -2412,13 +2004,12 @@ describe('given the load domain connections by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2432,9 +2023,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -2447,13 +2036,12 @@ describe('given the load domain connections by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2467,9 +2055,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -2485,16 +2071,13 @@ describe('given the load domain connections by user id function', () => { describe('given a database error', () => { describe('while querying domains', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query domains. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query domains. Please try again.')) const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2506,11 +2089,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible d'interroger le(s) domaine(s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible d'interroger le(s) domaine(s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -2533,6 +2112,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, i18n, }) @@ -2544,11 +2124,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api/src/domain/loaders/index.js b/api/src/domain/loaders/index.js new file mode 100644 index 0000000000..a34780f2e2 --- /dev/null +++ b/api/src/domain/loaders/index.js @@ -0,0 +1,5 @@ +export * from './load-domain-by-domain' +export * from './load-domain-by-key' +export * from './load-domain-connections-by-organizations-id' +export * from './load-domain-connections-by-user-id' +export * from './load-dkim-selectors-by-domain-id' diff --git a/api/src/domain/loaders/load-dkim-selectors-by-domain-id.js b/api/src/domain/loaders/load-dkim-selectors-by-domain-id.js new file mode 100644 index 0000000000..1dfa7ecbf8 --- /dev/null +++ b/api/src/domain/loaders/load-dkim-selectors-by-domain-id.js @@ -0,0 +1,34 @@ +import { t } from '@lingui/macro' + +export const loadDkimSelectorsByDomainId = + ({ query, userKey, i18n }) => + async ({ domainId }) => { + let domainSelectorsCursor + try { + domainSelectorsCursor = await query` + WITH domains, domainsToSelectors + FOR selector, selectorEdge IN 1..1 OUTBOUND ${domainId} domainsToSelectors + SORT selector.selector ASC + RETURN selector + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather domainSelectors in loadDomainSelectorsByDomainId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load domain selector(s). Please try again.`)) + } + + let domainSelectors + try { + domainSelectors = await domainSelectorsCursor.all() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather domainSelectors in loadDomainSelectorsByDomainId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load domain selector(s). Please try again.`)) + } + + return domainSelectors.map((selector) => { + return selector.selector + }) + } diff --git a/api-js/src/domain/loaders/load-domain-by-domain.js b/api/src/domain/loaders/load-domain-by-domain.js similarity index 90% rename from api-js/src/domain/loaders/load-domain-by-domain.js rename to api/src/domain/loaders/load-domain-by-domain.js index a85cd20020..cd2fe6fe42 100644 --- a/api-js/src/domain/loaders/load-domain-by-domain.js +++ b/api/src/domain/loaders/load-domain-by-domain.js @@ -1,7 +1,7 @@ import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadDomainByDomain = ({ query, userKey, i18n }) => +export const loadDomainByDomain = ({query, userKey, i18n}) => new DataLoader(async (domains) => { let cursor diff --git a/api-js/src/domain/loaders/load-domain-by-key.js b/api/src/domain/loaders/load-domain-by-key.js similarity index 90% rename from api-js/src/domain/loaders/load-domain-by-key.js rename to api/src/domain/loaders/load-domain-by-key.js index 03d08af21c..0d8e9da6c1 100644 --- a/api-js/src/domain/loaders/load-domain-by-key.js +++ b/api/src/domain/loaders/load-domain-by-key.js @@ -1,7 +1,7 @@ import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadDomainByKey = ({ query, userKey, i18n }) => +export const loadDomainByKey = ({query, userKey, i18n}) => new DataLoader(async (ids) => { let cursor diff --git a/api/src/domain/loaders/load-domain-connections-by-organizations-id.js b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js new file mode 100644 index 0000000000..14dd6d7a55 --- /dev/null +++ b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js @@ -0,0 +1,445 @@ +import { aql } from 'arangojs' +import { fromGlobalId, toGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' +import { buildDomainFilters } from '../helpers' + +export const loadDomainConnectionsByOrgId = + ({ query, userKey, cleanseInput, i18n, auth: { loginRequiredBool } }) => + async ({ orgId, permission, after, before, first, last, ownership, orderBy, search, filters = [] }) => { + let afterTemplate = aql`` + let afterVar = aql`` + + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(domains, ${afterId})` + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`afterVar.domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`afterVar.status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`afterVar.status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`afterVar.status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`afterVar.status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ciphers-status') { + documentField = aql`afterVar.status.ciphers` + domainField = aql`domain.status.ciphers` + } else if (orderBy.field === 'curves-status') { + documentField = aql`afterVar.status.curves` + domainField = aql`domain.status.curves` + } else if (orderBy.field === 'hsts-status') { + documentField = aql`afterVar.status.hsts` + domainField = aql`domain.status.hsts` + } else if (orderBy.field === 'policy-status') { + documentField = aql`afterVar.status.policy` + domainField = aql`domain.status.policy` + } else if (orderBy.field === 'protocols-status') { + documentField = aql`afterVar.status.protocols` + domainField = aql`domain.status.protocols` + } else if (orderBy.field === 'certificates-status') { + documentField = aql`afterVar.status.certificates` + domainField = aql`domain.status.certificates` + } + + afterTemplate = aql` + FILTER ${domainField} ${afterTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(domains, ${beforeId})` + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`beforeVar.domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`beforeVar.status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`beforeVar.status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`beforeVar.status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`beforeVar.status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ciphers-status') { + documentField = aql`beforeVar.status.ciphers` + domainField = aql`domain.status.ciphers` + } else if (orderBy.field === 'curves-status') { + documentField = aql`beforeVar.status.curves` + domainField = aql`domain.status.curves` + } else if (orderBy.field === 'hsts-status') { + documentField = aql`beforeVar.status.hsts` + domainField = aql`domain.status.hsts` + } else if (orderBy.field === 'policy-status') { + documentField = aql`beforeVar.status.policy` + domainField = aql`domain.status.policy` + } else if (orderBy.field === 'protocols-status') { + documentField = aql`beforeVar.status.protocols` + domainField = aql`domain.status.protocols` + } else if (orderBy.field === 'certificates-status') { + documentField = aql`beforeVar.status.certificates` + domainField = aql`domain.status.certificates` + } + + beforeTemplate = aql` + FILTER ${domainField} ${beforeTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDomainConnectionsByOrgId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Domain\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDomainConnectionsByOrgId.`, + ) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDomainConnectionsByOrgId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` on the \`Domain\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn(`User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadDomainConnectionsByOrgId.`) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Domain\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(domain._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(domain._key) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDomainConnectionsByOrgId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let domainField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + domainField = aql`domain.domain` + hasNextPageDocumentField = aql`LAST(retrievedDomains).domain` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).domain` + } else if (orderBy.field === 'last-ran') { + domainField = aql`domain.lastRan` + hasNextPageDocumentField = aql`LAST(retrievedDomains).lastRan` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).lastRan` + } else if (orderBy.field === 'dkim-status') { + domainField = aql`domain.status.dkim` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dkim` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dkim` + } else if (orderBy.field === 'dmarc-status') { + domainField = aql`domain.status.dmarc` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dmarc` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dmarc` + } else if (orderBy.field === 'https-status') { + domainField = aql`domain.status.https` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.https` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.https` + } else if (orderBy.field === 'spf-status') { + domainField = aql`domain.status.spf` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.spf` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.spf` + } else if (orderBy.field === 'ssl-status') { + domainField = aql`domain.status.ssl` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.ssl` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.ssl` + } else if (orderBy.field === 'ciphers-status') { + domainField = aql`domain.status.ciphers` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.ciphers` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.ciphers` + } else if (orderBy.field === 'curves-status') { + domainField = aql`domain.status.curves` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.curves` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.curves` + } else if (orderBy.field === 'hsts-status') { + domainField = aql`domain.status.hsts` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.hsts` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.hsts` + } else if (orderBy.field === 'policy-status') { + domainField = aql`domain.status.policy` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.policy` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.policy` + } else if (orderBy.field === 'protocols-status') { + domainField = aql`domain.status.protocols` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.protocols` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.protocols` + } else if (orderBy.field === 'certificates-status') { + domainField = aql`domain.status.certificates` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.certificates` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.certificates` + } + + hasNextPageFilter = aql` + FILTER ${domainField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${domainField} == ${hasNextPageDocumentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${domainField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${domainField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + sortByField = aql`domain.domain ${orderBy.direction},` + } else if (orderBy.field === 'dkim-status') { + sortByField = aql`domain.status.dkim ${orderBy.direction},` + } else if (orderBy.field === 'dmarc-status') { + sortByField = aql`domain.status.dmarc ${orderBy.direction},` + } else if (orderBy.field === 'https-status') { + sortByField = aql`domain.status.https ${orderBy.direction},` + } else if (orderBy.field === 'spf-status') { + sortByField = aql`domain.status.spf ${orderBy.direction},` + } else if (orderBy.field === 'ciphers-status') { + sortByField = aql`domain.status.ciphers ${orderBy.direction},` + } else if (orderBy.field === 'curves-status') { + sortByField = aql`domain.status.curves ${orderBy.direction},` + } else if (orderBy.field === 'hsts-status') { + sortByField = aql`domain.status.hsts ${orderBy.direction},` + } else if (orderBy.field === 'policy-status') { + sortByField = aql`domain.status.policy ${orderBy.direction},` + } else if (orderBy.field === 'protocols-status') { + sortByField = aql`domain.status.protocols ${orderBy.direction},` + } else if (orderBy.field === 'certificates-status') { + sortByField = aql`domain.status.certificates ${orderBy.direction},` + } + } + + const domainFilters = buildDomainFilters({ filters }) + + let domainQuery = aql`` + let loopString = aql`FOR domain IN collectedDomains` + let totalCount = aql`LENGTH(collectedDomains)` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + domainQuery = aql` + LET searchedDomains = ( + FOR domain IN collectedDomains + FILTER LOWER(domain.domain) LIKE LOWER(${search}) + RETURN MERGE({ id: domain._key, _type: "domain" }, domain) + ) + ` + loopString = aql`FOR domain IN searchedDomains` + totalCount = aql`LENGTH(searchedDomains)` + } + + let showArchivedDomains = aql`FILTER v.archived != true` + if (permission === 'super_admin') { + showArchivedDomains = aql`` + } + + let ownershipOrgsOnly = aql` + FOR v, e IN 1..1 OUTBOUND ${orgId} claims + OPTIONS {order: "bfs"} + RETURN v + ` + if (typeof ownership !== 'undefined') { + if (ownership) { + ownershipOrgsOnly = aql` + FOR v, e IN 1..1 OUTBOUND ${orgId} ownership + OPTIONS {order: "bfs"} + RETURN v + ` + } + } + + let requestedDomainInfo + try { + let collectedDomains + if (!loginRequiredBool) { + collectedDomains = aql` + LET collectedDomains = ( + FOR v, e IN 1..1 OUTBOUND ${orgId} claims + OPTIONS {order: "bfs"} + LET negativeTags = APPEND(v.negativeTags.dns, v.negativeTags.web) + ${showArchivedDomains} + ${domainFilters} + RETURN v + )` + } else { + collectedDomains = aql` + LET collectedDomains = UNIQUE( + ${ownershipOrgsOnly} + )` + } + + requestedDomainInfo = await query` + WITH affiliations, domains, organizations, users + + ${collectedDomains} + + ${domainQuery} + + ${afterVar} + ${beforeVar} + + LET retrievedDomains = ( + ${loopString} + LET claimVals = ( + FOR v, e IN 1..1 ANY domain._id claims + FILTER e._from == ${orgId} + RETURN { assetState: e.assetState, claimTags: e.tags } + )[0] + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE({ id: domain._key, _type: "domain", "claimTags": claimVals.claimTags, "assetState": claimVals.assetState }, DOCUMENT(domain._id)) + ) + + LET hasNextPage = (LENGTH( + ${loopString} + ${hasNextPageFilter} + LIMIT 1 + RETURN domain + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + ${loopString} + ${hasPreviousPageFilter} + LIMIT 1 + RETURN domain + ) > 0 ? true : false) + + RETURN { + "domains": retrievedDomains, + "totalCount": ${totalCount}, + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedDomains)._key, + "endKey": LAST(retrievedDomains)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather domains in loadDomainConnectionsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) + } + + let domainsInfo + try { + domainsInfo = await requestedDomainInfo.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather domains in loadDomainConnectionsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) + } + + if (domainsInfo.domains.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = domainsInfo.domains.map((domain) => { + return { + cursor: toGlobalId('domain', domain._key), + node: domain, + } + }) + + return { + edges, + totalCount: domainsInfo.totalCount, + pageInfo: { + hasNextPage: domainsInfo.hasNextPage, + hasPreviousPage: domainsInfo.hasPreviousPage, + startCursor: toGlobalId('domain', domainsInfo.startKey), + endCursor: toGlobalId('domain', domainsInfo.endKey), + }, + } + } diff --git a/api/src/domain/loaders/load-domain-connections-by-user-id.js b/api/src/domain/loaders/load-domain-connections-by-user-id.js new file mode 100644 index 0000000000..b15b39d901 --- /dev/null +++ b/api/src/domain/loaders/load-domain-connections-by-user-id.js @@ -0,0 +1,502 @@ +import { aql } from 'arangojs' +import { fromGlobalId, toGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' +import { buildDomainFilters } from '../helpers' + +export const loadDomainConnectionsByUserId = + ({ query, userKey, cleanseInput, i18n, auth: { loginRequiredBool } }) => + async ({ + after, + before, + first, + last, + ownership, + orderBy, + isSuperAdmin, + myTracker, + search, + isAffiliated, + filters = [], + }) => { + const userDBId = `users/${userKey}` + + let ownershipOrgsOnly = aql` + FOR v, e IN 1..1 OUTBOUND org._id claims + ` + if (typeof ownership !== 'undefined') { + if (ownership) { + ownershipOrgsOnly = aql` + FOR v, e IN 1..1 OUTBOUND org._id ownership + ` + } + } + + let afterTemplate = aql`` + let afterVar = aql`` + + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(domains, ${afterId})` + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`afterVar.domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`afterVar.status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`afterVar.status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`afterVar.status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`afterVar.status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ciphers-status') { + documentField = aql`afterVar.status.ciphers` + domainField = aql`domain.status.ciphers` + } else if (orderBy.field === 'curves-status') { + documentField = aql`afterVar.status.curves` + domainField = aql`domain.status.curves` + } else if (orderBy.field === 'hsts-status') { + documentField = aql`afterVar.status.hsts` + domainField = aql`domain.status.hsts` + } else if (orderBy.field === 'policy-status') { + documentField = aql`afterVar.status.policy` + domainField = aql`domain.status.policy` + } else if (orderBy.field === 'protocols-status') { + documentField = aql`afterVar.status.protocols` + domainField = aql`domain.status.protocols` + } else if (orderBy.field === 'certificates-status') { + documentField = aql`afterVar.status.certificates` + domainField = aql`domain.status.certificates` + } + + afterTemplate = aql` + FILTER ${domainField} ${afterTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(domains, ${beforeId})` + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`beforeVar.domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`beforeVar.status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`beforeVar.status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`beforeVar.status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`beforeVar.status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ciphers-status') { + documentField = aql`beforeVar.status.ciphers` + domainField = aql`domain.status.ciphers` + } else if (orderBy.field === 'curves-status') { + documentField = aql`beforeVar.status.curves` + domainField = aql`domain.status.curves` + } else if (orderBy.field === 'hsts-status') { + documentField = aql`beforeVar.status.hsts` + domainField = aql`domain.status.hsts` + } else if (orderBy.field === 'policy-status') { + documentField = aql`beforeVar.status.policy` + domainField = aql`domain.status.policy` + } else if (orderBy.field === 'protocols-status') { + documentField = aql`beforeVar.status.protocols` + domainField = aql`domain.status.protocols` + } else if (orderBy.field === 'certificates-status') { + documentField = aql`beforeVar.status.certificates` + domainField = aql`domain.status.certificates` + } + + beforeTemplate = aql` + FILTER ${domainField} ${beforeTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDomainConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Domain\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDomainConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDomainConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` on the \`Domain\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDomainConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Domain\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(domain._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(domain._key) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDomainConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let domainField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + domainField = aql`domain.domain` + hasNextPageDocumentField = aql`LAST(retrievedDomains).domain` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).domain` + } else if (orderBy.field === 'last-ran') { + domainField = aql`domain.lastRan` + hasNextPageDocumentField = aql`LAST(retrievedDomains).lastRan` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).lastRan` + } else if (orderBy.field === 'dkim-status') { + domainField = aql`domain.status.dkim` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dkim` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dkim` + } else if (orderBy.field === 'dmarc-status') { + domainField = aql`domain.status.dmarc` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.dmarc` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.dmarc` + } else if (orderBy.field === 'https-status') { + domainField = aql`domain.status.https` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.https` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.https` + } else if (orderBy.field === 'spf-status') { + domainField = aql`domain.status.spf` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.spf` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.spf` + } else if (orderBy.field === 'ssl-status') { + domainField = aql`domain.status.ssl` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.ssl` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.ssl` + } else if (orderBy.field === 'ciphers-status') { + domainField = aql`domain.status.ciphers` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.ciphers` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.ciphers` + } else if (orderBy.field === 'curves-status') { + domainField = aql`domain.status.curves` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.curves` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.curves` + } else if (orderBy.field === 'hsts-status') { + domainField = aql`domain.status.hsts` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.hsts` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.hsts` + } else if (orderBy.field === 'policy-status') { + domainField = aql`domain.status.policy` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.policy` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.policy` + } else if (orderBy.field === 'protocols-status') { + domainField = aql`domain.status.protocols` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.protocols` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.protocols` + } else if (orderBy.field === 'certificates-status') { + domainField = aql`domain.status.certificates` + hasNextPageDocumentField = aql`LAST(retrievedDomains).status.certificates` + hasPreviousPageDocumentField = aql`FIRST(retrievedDomains).status.certificates` + } + + hasNextPageFilter = aql` + FILTER ${domainField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${domainField} == ${hasNextPageDocumentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${domainField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${domainField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + sortByField = aql`domain.domain ${orderBy.direction},` + } else if (orderBy.field === 'dkim-status') { + sortByField = aql`domain.status.dkim ${orderBy.direction},` + } else if (orderBy.field === 'dmarc-status') { + sortByField = aql`domain.status.dmarc ${orderBy.direction},` + } else if (orderBy.field === 'https-status') { + sortByField = aql`domain.status.https ${orderBy.direction},` + } else if (orderBy.field === 'spf-status') { + sortByField = aql`domain.status.spf ${orderBy.direction},` + } else if (orderBy.field === 'ciphers-status') { + sortByField = aql`domain.status.ciphers ${orderBy.direction},` + } else if (orderBy.field === 'curves-status') { + sortByField = aql`domain.status.curves ${orderBy.direction},` + } else if (orderBy.field === 'hsts-status') { + sortByField = aql`domain.status.hsts ${orderBy.direction},` + } else if (orderBy.field === 'policy-status') { + sortByField = aql`domain.status.policy ${orderBy.direction},` + } else if (orderBy.field === 'protocols-status') { + sortByField = aql`domain.status.protocols ${orderBy.direction},` + } else if (orderBy.field === 'certificates-status') { + sortByField = aql`domain.status.certificates ${orderBy.direction},` + } + } + + const domainFilters = buildDomainFilters({ filters }) + + let domainKeysQuery + if (myTracker) { + domainKeysQuery = aql` + WITH favourites, users, domains + LET collectedDomains = ( + FOR v, e IN 1..1 OUTBOUND ${userDBId} favourites + OPTIONS {order: "bfs"} + RETURN v + ) + ` + } else if (isSuperAdmin) { + domainKeysQuery = aql` + WITH affiliations, domains, organizations, users, domainSearch, claims, ownership + LET collectedDomains = UNIQUE( + FOR org IN organizations + ${ownershipOrgsOnly} + ${domainFilters} + RETURN v + ) + ` + } else if (isAffiliated) { + domainKeysQuery = aql` + WITH affiliations, domains, organizations, users, domainSearch, claims, ownership + LET collectedDomains = UNIQUE( + LET userAffiliations = ( + FOR v, e IN 1..1 INBOUND ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key + ${ownershipOrgsOnly} + FILTER v.archived != true + ${domainFilters} + RETURN v + ) + ` + } else if (!loginRequiredBool) { + domainKeysQuery = aql` + WITH affiliations, domains, organizations, users, domainSearch, claims, ownership + LET collectedDomains = UNIQUE( + LET userAffiliations = ( + FOR v, e IN 1..1 INBOUND ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key || org.verified == true + ${ownershipOrgsOnly} + FILTER v.archived != true + ${domainFilters} + RETURN v + ) + ` + } else { + domainKeysQuery = aql` + WITH affiliations, domains, organizations, users, domainSearch, claims, ownership + LET collectedDomains = UNIQUE( + LET userAffiliations = ( + FOR v, e IN 1..1 INBOUND ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key || (hasVerifiedOrgAffiliation == true && org.verified == true) + ${ownershipOrgsOnly} + FILTER v.archived != true + ${domainFilters} + RETURN v + ) + ` + } + + let domainQuery = aql`` + let loopString = aql`FOR domain IN collectedDomains` + let totalCount = aql`LENGTH(collectedDomains)` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + domainQuery = aql` + LET searchedDomains = ( + FOR domain IN collectedDomains + FILTER LOWER(domain.domain) LIKE LOWER(${search}) + RETURN domain + ) + ` + loopString = aql`FOR domain IN searchedDomains` + totalCount = aql`LENGTH(searchedDomains)` + } + + let requestedDomainInfo + try { + requestedDomainInfo = await query` + ${domainKeysQuery} + + ${domainQuery} + + ${afterVar} + ${beforeVar} + + LET retrievedDomains = ( + ${loopString} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE({ id: domain._key, _type: "domain" }, domain) + ) + + LET hasNextPage = (LENGTH( + ${loopString} + ${hasNextPageFilter} + LIMIT 1 + RETURN domain + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + ${loopString} + ${hasPreviousPageFilter} + LIMIT 1 + RETURN domain + ) > 0 ? true : false) + + RETURN { + "domains": retrievedDomains, + "totalCount": ${totalCount}, + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedDomains)._key, + "endKey": LAST(retrievedDomains)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query domains in loadDomainsByUser, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to query domain(s). Please try again.`)) + } + + let domainsInfo + try { + domainsInfo = await requestedDomainInfo.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather domains in loadDomainsByUser, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) + } + + if (domainsInfo.domains.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = domainsInfo.domains.map((domain) => { + return { + cursor: toGlobalId('domain', domain._key), + node: domain, + } + }) + + return { + edges, + totalCount: domainsInfo.totalCount, + pageInfo: { + hasNextPage: domainsInfo.hasNextPage, + hasPreviousPage: domainsInfo.hasPreviousPage, + startCursor: toGlobalId('domain', domainsInfo.startKey), + endCursor: toGlobalId('domain', domainsInfo.endKey), + }, + } + } diff --git a/api/src/domain/mutations/__tests__/add-organizations-domains.test.js b/api/src/domain/mutations/__tests__/add-organizations-domains.test.js new file mode 100644 index 0000000000..7969ba2e87 --- /dev/null +++ b/api/src/domain/mutations/__tests__/add-organizations-domains.test.js @@ -0,0 +1,397 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, saltedHash, verifiedRequired, tfaRequired } from '../../../auth' +import { loadDomainByDomain } from '../../loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env + +describe('given the addOrganizationsDomains mutation', () => { + let query, drop, i18n, truncate, schema, collections, transaction, user, org + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful bulk domain creation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('user has super admin permission level', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it('creates domains', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + addOrganizationsDomains( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + domains: ["test.domain.gov", "test.domain2.gov"] + tagNewDomains: false + tagStagingDomains: false + audit: false + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + addOrganizationsDomains: { + result: { + status: `Successfully added 2 domain(s) to treasury-board-secretariat.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully added 2 domain(s) to org: treasury-board-secretariat.`, + ]) + }) + describe('audit flag is true', () => { + it('creates additional logs for each domain added', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + addOrganizationsDomains( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + domains: ["test.domain.gov", "test.domain2.gov"] + tagNewDomains: false + tagStagingDomains: false + audit: true + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + addOrganizationsDomains: { + result: { + status: `Successfully added 2 domain(s) to treasury-board-secretariat.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully added domain: test.domain.gov to org: treasury-board-secretariat.`, + `User: ${user._key} successfully added domain: test.domain2.gov to org: treasury-board-secretariat.`, + ]) + }) + }) + }) + }) + + describe('given an unsuccessful bulk domain creation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('user does not have permission to add domains', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + addOrganizationsDomains( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + domains: ["test.domain.gov", "test.domain2.gov"] + tagNewDomains: false + tagStagingDomains: false + audit: false + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + addOrganizationsDomains: { + result: { + code: 400, + description: `Permission Denied: Please contact organization user for help with creating domains.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/create-domain.test.js b/api/src/domain/mutations/__tests__/create-domain.test.js new file mode 100644 index 0000000000..26c60b9e6b --- /dev/null +++ b/api/src/domain/mutations/__tests__/create-domain.test.js @@ -0,0 +1,2389 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput, slugify } from '../../../validators' +import { + checkPermission, + userRequired, + checkSuperAdmin, + saltedHash, + verifiedRequired, + tfaRequired, + checkDomainPermission, + AuthDataSource, +} from '../../../auth' +import { loadDkimSelectorsByDomainId, loadDomainByDomain } from '../../loaders' +import { loadOrgByKey, loadOrgConnectionsByDomainId } from '../../../organization/loaders' +import { OrganizationDataSource } from '../../../organization/data-source' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env + +describe('create a domain', () => { + let query, drop, truncate, schema, collections, transaction, user, org, domain + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + + const i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful domain creation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + domain = ( + await collections.domains.save( + { + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], + status: { + dkim: 'info', + dmarc: 'info', + https: 'info', + spf: 'info', + ssl: 'info', + }, + }, + { returnNew: true }, + ) + ).new + + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('user has super admin permission level', () => { + describe('user belongs to the same org', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + loginRequiredBool: true, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + query, + language: 'en', + userKey: user._key, + cleanseInput, + auth: { loginRequiredBool: true }, + }), + loadUserByKey: loadUserByKey({ query }), + }, + validators: { cleanseInput, slugify }, + }, + }) + + const expectedResponse = { + data: { + createDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], + status: { + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', + }, + organizations: { + edges: [ + { + node: { + id: toGlobalId('organization', org._key), + name: 'Treasury Board of Canada Secretariat', + }, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, + ]) + }) + }) + describe('user belongs to a different org', () => { + let secondOrg + beforeEach(async () => { + secondOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'communications-security-establishment', + acronym: 'CSE', + name: 'Communications Security Establishment', + zone: 'FED', + sector: 'DND', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'centre-de-la-securite-des-telecommunications', + acronym: 'CST', + name: 'Centre de la Securite des Telecommunications', + zone: 'FED', + sector: 'DND', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ + _from: secondOrg._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + loginRequiredBool: true, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + query, + language: 'en', + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + }), + loadUserByKey: loadUserByKey({ query }), + }, + validators: { cleanseInput, slugify }, + }, + }) + + const expectedResponse = { + data: { + createDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], + status: { + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', + }, + organizations: { + edges: [ + { + node: { + id: toGlobalId('organization', org._key), + name: 'Treasury Board of Canada Secretariat', + }, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, + ]) + }) + }) + }) + describe('user has admin permission level', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + }) + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + loginRequiredBool: true, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + query, + language: 'en', + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + }), + loadUserByKey: loadUserByKey({ query }), + }, + validators: { cleanseInput, slugify }, + }, + }) + + const expectedResponse = { + data: { + createDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], + status: { + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', + }, + organizations: { + edges: [ + { + node: { + id: toGlobalId('organization', org._key), + name: 'Treasury Board of Canada Secretariat', + }, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, + ]) + }) + }) + describe('domain can be created in a different organization', () => { + let secondOrg + beforeEach(async () => { + secondOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'communications-security-establishment', + acronym: 'CSE', + name: 'Communications Security Establishment', + zone: 'FED', + sector: 'DND', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'centre-de-la-securite-des-telecommunications', + acronym: 'CST', + name: 'Centre de la Securite des Telecommunications', + zone: 'FED', + sector: 'DND', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ + _from: secondOrg._id, + _to: user._id, + permission: 'super_admin', + }) + }) + beforeEach(async () => { + const selector1 = await collections.selectors.save({ selector: 'selector1' }) + const selector2 = await collections.selectors.save({ selector: 'selector2' }) + await collections.domainsToSelectors.save({ + _from: domain._id, + _to: selector1._id, + }) + await collections.domainsToSelectors.save({ + _from: domain._id, + _to: selector2._id, + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', secondOrg._key)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + loginRequiredBool: true, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + query, + language: 'en', + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + }), + loadUserByKey: loadUserByKey({ query }), + }, + validators: { cleanseInput, slugify }, + }, + }) + + const expectedResponse = { + data: { + createDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: ['selector1', 'selector2'], + status: { + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', + }, + organizations: { + edges: [ + { + node: { + id: toGlobalId('organization', org._key), + name: 'Treasury Board of Canada Secretariat', + }, + }, + { + node: { + id: toGlobalId('organization', secondOrg._key), + name: 'Communications Security Establishment', + }, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, + ]) + }) + describe('lastRan is not changed', () => { + beforeEach(async () => { + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', secondOrg._key)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + loginRequiredBool: true, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + query, + language: 'en', + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + }), + loadUserByKey: loadUserByKey({ query }), + }, + validators: { cleanseInput, slugify }, + }, + }) + + const expectedResponse = { + data: { + createDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: ['selector1', 'selector2'], + status: { + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', + }, + organizations: { + edges: [ + { + node: { + id: toGlobalId('organization', org._key), + name: 'Treasury Board of Canada Secretariat', + }, + }, + { + node: { + id: toGlobalId('organization', secondOrg._key), + name: 'Communications Security Establishment', + }, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, + ]) + }) + }) + describe('status do not changed', () => { + beforeEach(async () => { + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', secondOrg._key)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + loginRequiredBool: true, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + query, + language: 'en', + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + }), + loadUserByKey: loadUserByKey({ query }), + }, + validators: { cleanseInput, slugify }, + }, + }) + + const expectedResponse = { + data: { + createDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: '2021-01-01 12:00:00.000000', + selectors: ['selector1', 'selector2'], + status: { + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', + }, + organizations: { + edges: [ + { + node: { + id: toGlobalId('organization', org._key), + name: 'Treasury Board of Canada Secretariat', + }, + }, + { + node: { + id: toGlobalId('organization', secondOrg._key), + name: 'Communications Security Establishment', + }, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, + ]) + }) + }) + }) + }) + describe('given an unsuccessful domain creation', () => { + let i18n + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('org does not exist', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { orgId: "b3JnYW5pemF0aW9uOjE=", domain: "test.gc.ca", assetState: APPROVED, cvdEnrollment: { status: ENROLLED } } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + cvdEnrollment { + status + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn(), + saltedHash: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = { + data: { + createDomain: { + result: { + code: 400, + description: 'Unable to create domain in unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to create a domain to an organization: 1 that does not exist.`, + ]) + }) + + it('returns the domain with cvdEnrollment status not-enrolled when not provided', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { orgId: "b3JnYW5pemF0aW9uOjE=", domain: "test.gc.ca", assetState: APPROVED } + ) { + result { + ... on Domain { + cvdEnrollment { + status + } + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn(), + saltedHash: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + // Should default to not-enrolled + if ( + response.data && + response.data.createDomain && + response.data.createDomain.result && + response.data.createDomain.result.cvdEnrollment + ) { + expect(response.data.createDomain.result.cvdEnrollment.status).toBe('not-enrolled') + } + }) + }) + }) + describe('user does not belong to organization', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue(undefined), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = { + data: { + createDomain: { + result: { + code: 400, + description: 'Permission Denied: Please contact organization user for help with creating domain.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to create a domain in: treasury-board-secretariat, however they do not have permission to do so.`, + ]) + }) + }) + describe('the domain already exists in the given organization', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValue({}), + }), + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = { + data: { + createDomain: { + result: { + code: 400, + description: 'Unable to create domain, organization has already claimed it.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to create a domain for: treasury-board-secretariat, however that org already has that domain claimed.`, + ]) + }) + }) + describe('database error occurs', () => { + describe('when checking to see if org already contains domain', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Database error occurred while running check to see if domain already exists in an org: Error: Database error occurred.`, + ]) + }) + }) + }) + describe('cursor error occurs', () => { + describe('when checking to see if org already contains domain', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), + }), + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Cursor error occurred while running check to see if domain already exists in an org: Error: Cursor error occurred.`, + ]) + }) + }) + describe('when gathering inserted domain', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValue(undefined), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValueOnce({ + next: jest.fn().mockRejectedValue(new Error('cursor error')), + }), + abort: jest.fn(), + }), + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Cursor error occurred for user: 123 when inserting new domain: Error: cursor error`, + ]) + }) + }) + }) + describe('transaction step error occurs', () => { + describe('when creating a new domain', () => { + describe('when inserting new domain', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValue(undefined), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), + }), + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred for user: 123 when inserting new domain: Error: trx step error`, + ]) + }) + }) + describe('when inserting new edge', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValue(undefined), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest + .fn() + .mockReturnValueOnce({ + next: jest.fn(), + }) + .mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), + }), + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred for user: 123 when inserting new domain edge: Error: trx step error`, + ]) + }) + }) + }) + describe('when domain already exists', () => { + describe('when upserting domain', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValueOnce(undefined), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), + }), + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn().mockReturnValue({ + domain: 'domain.ca', + selectors: [], + status: {}, + lastRan: '', + }), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred for user: 123 when inserting new domain: Error: trx step error`, + ]) + }) + }) + describe('when inserting edge to new org', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValueOnce(undefined), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest + .fn() + .mockReturnValueOnce({ next: jest.fn().mockReturnValue() }) + .mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), + }), + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: 123, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: 123, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn().mockReturnValue({ + domain: 'domain.ca', + selectors: [], + status: {}, + lastRan: '', + }), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred for user: 123 when inserting new domain edge: Error: trx step error`, + ]) + }) + }) + }) + }) + describe('transaction commit error occurs', () => { + describe('when committing transaction', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createDomain( + input: { + orgId: "${toGlobalId('organization', 123)}" + domain: "test.gc.ca" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + status { + dkim + dmarc + https + spf + ssl + } + organizations(first: 5) { + edges { + node { + id + name + } + } + } + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query: jest.fn().mockReturnValue({ + next: jest.fn().mockReturnValue(undefined), + }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ next: jest.fn().mockReturnValue() }), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), + }), + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: 123, + query, + }), + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + saltedHash: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: 123, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByDomain: { + load: jest.fn().mockReturnValue({ + domain: 'domain.ca', + selectors: [], + status: {}, + lastRan: '', + }), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', + }), + }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction commit error occurred while user: 123 was creating domain: Error: trx commit error`, + ]) + }) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/favourite-domain.test.js b/api/src/domain/mutations/__tests__/favourite-domain.test.js new file mode 100644 index 0000000000..2310b05235 --- /dev/null +++ b/api/src/domain/mutations/__tests__/favourite-domain.test.js @@ -0,0 +1,133 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { userRequired, verifiedRequired } from '../../../auth' +import { loadDomainByKey } from '../../loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env + +describe('favourite a domain', () => { + let query, drop, truncate, schema, collections, transaction, user, domain1 + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful domain favourite', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + domain1 = await collections.domains.save({ + domain: 'test.gc.ca', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('user is logged in and verified', () => { + describe('user favourites a domain', () => { + it('returns the domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + favouriteDomain(input: { domainId: "${toGlobalId('domain', domain1._key)}" }) { + result { + ... on Domain { + domain + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + loaders: { + loadDomainByKey: loadDomainByKey({ + query, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const domainCursor = await query` + FOR domain IN domains + RETURN domain + ` + const domain = await domainCursor.next() + const expectedResponse = { + data: { + favouriteDomain: { + result: { domain: 'test.gc.ca' }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([`User: ${user._key} successfully favourited domain ${domain.domain}.`]) + }) + }) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/ignore-cve.test.js b/api/src/domain/mutations/__tests__/ignore-cve.test.js new file mode 100644 index 0000000000..ee8fca8728 --- /dev/null +++ b/api/src/domain/mutations/__tests__/ignore-cve.test.js @@ -0,0 +1,351 @@ +import { graphql, GraphQLError, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { createMutationSchema } from '../../../mutation' +import { createUserContextGenerator, ensureDatabase as ensure } from '../../../testUtilities' +import { dbNameFromFile } from 'arango-tools' +import dbschema from '../../../../../database-migration/database.json' +import { createQuerySchema } from '../../../query' +import { createI18n } from '../../../create-i18n' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, AUTHENTICATED_KEY, HASHING_SALT } = process.env + +const schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), +}) +const consoleOutput = [] +const mockedInfo = (output) => consoleOutput.push(output) +const mockedWarn = (output) => consoleOutput.push(output) +const mockedError = (output) => consoleOutput.push(output) +console.info = mockedInfo +console.warn = mockedWarn +console.error = mockedError + +const i18n = createI18n('en') + +let db, + query, + drop, + truncate, + collections, + transaction, + createUserContext, + domain, + superAdminUser, + superAdminContext, + normalUser, + normalUserContext + +describe('ignoreCve mutation', () => { + beforeAll(async () => { + ;({ db, query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + + createUserContext = createUserContextGenerator({ + db, + query, + transaction, + collectionNames, + i18n, + secret: AUTHENTICATED_KEY, + salt: HASHING_SALT, + }) + }) + + beforeEach(async () => { + superAdminUser = ( + await collections.users.save( + { + _key: 'superadminuser', + userName: 'sueradminuser@test.gc.ca', + emailValidated: true, + }, + { returnNew: true }, + ) + ).new + superAdminContext = await createUserContext({ userKey: superAdminUser._key }) + normalUser = ( + await collections.users.save( + { + _key: 'normaluser', + userName: 'normaluser@test.gc.ca', + emailValidated: true, + }, + { returnNew: true }, + ) + ).new + normalUserContext = await createUserContext({ userKey: normalUser._key }) + + const superAdminOrg = ( + await collections.organizations.save( + { + _key: 'superadminorg', + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + }, + }, + }, + { returnNew: true }, + ) + ).new + await collections.affiliations.save( + { + _from: superAdminOrg._id, + _to: superAdminUser._id, + permission: 'super_admin', + }, + { returnNew: true }, + ) + + domain = ( + await collections.domains.save( + { + _key: '123', + domain: 'test.domain.gc.ca', + slug: 'test-domain-gc-ca', + }, + { returnNew: true }, + ) + ).new + }) + + afterEach(async () => { + consoleOutput.length = 0 + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + it('returns an error when the user is not a super admin', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + ignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "CVE-1234-55555" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: normalUserContext, + }) + + const expectedError = 'Permissions error. You do not have sufficient permissions to access this data.' + expect(response.errors[0].message).toEqual(expectedError) + + const expectConsoleOutput = [ + `User: ${normalUser._key} attempted to access controlled functionality without sufficient privileges.`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + describe('given a super admin user', () => { + it('returns an error when the domain does not exist', async () => { + const badDomainKey = 'bad-domain-key' + const cve = 'CVE-1234-55555' + const response = await graphql({ + schema, + source: ` + mutation { + ignoreCve(input: { domainId: "${toGlobalId('domain', badDomainKey)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const expectedError = { + code: 400, + description: 'Unable to ignore CVE. Please try again.', + } + expect(response.data.ignoreCve.result).toEqual(expectedError) + + const expectConsoleOutput = [ + `User: "${superAdminUser._key}" attempted to ignore CVE "${cve}" on unknown domain: "${badDomainKey}".`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('returns an error when the CVE is already ignored', async () => { + const cve = 'CVE-1234-55555' + // Add the CVE to the domain's ignoredCves array + await query` + UPDATE { + _key: ${domain._key}, + ignoredCves: ${[cve]} + } IN domains + ` + const response = await graphql({ + schema, + source: ` + mutation { + ignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const expectedError = { + code: 400, + description: 'CVE is already ignored for this domain.', + } + expect(response.data.ignoreCve.result).toEqual(expectedError) + + const expectConsoleOutput = [ + `User: "${superAdminUser._key}" attempted to ignore CVE "${cve}" on domain: "${domain._key}" however CVE is already ignored.`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('throws an error when the transaction step fails', async () => { + const cve = 'CVE-1234-55555' + superAdminContext.transaction = jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + ignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const error = [new GraphQLError('Unable to ignore CVE. Please try again.')] + expect(response.errors).toEqual(error) + + const expectConsoleOutput = [ + `Transaction step error occurred when user: "${superAdminUser._key}" attempted to ignore CVE "${cve}" on domain "${domain._key}", error: Error: Transaction step error`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('throws an error when the transaction commit fails', async () => { + const cve = 'CVE-1234-55555' + superAdminContext.transaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + ignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const error = [new GraphQLError('Unable to ignore CVE. Please try again.')] + expect(response.errors).toEqual(error) + + const expectConsoleOutput = [ + `Transaction commit error occurred when user: "${superAdminUser._key}" attempted to ignore CVE "${cve}" on domain "${domain._key}", error: Error: Transaction commit error`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('successfully ignores a CVE', async () => { + // Ensure CSV is not already ignored + const currentDomainState = await (await query`RETURN DOCUMENT(domains, ${domain._key}).ignoredCves || []`).next() + expect(currentDomainState).toEqual([]) + + const cve = 'CVE-1234-55555' + const response = await graphql({ + schema, + source: ` + mutation { + ignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on Domain { + domain + ignoredCves + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const expectedResponse = { + data: { + ignoreCve: { + result: { + domain: domain.domain, + ignoredCves: [cve], + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + const domainCursor = await query` + FOR domain IN domains + FILTER domain._key == ${domain._key} + RETURN domain + ` + const domainArr = await domainCursor.all() + const domainObj = domainArr[0] + expect(domainObj.ignoredCves).toEqual([cve]) + + const expectConsoleOutput = [ + `User: "${superAdminUser._key}" successfully ignored CVE "${cve}" on domain: "${domain._key}".`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + }) +}) diff --git a/api-js/src/domain/mutations/__tests__/remove-domain.test.js b/api/src/domain/mutations/__tests__/remove-domain.test.js similarity index 78% rename from api-js/src/domain/mutations/__tests__/remove-domain.test.js rename to api/src/domain/mutations/__tests__/remove-domain.test.js index 9675fd293e..339bdeb572 100644 --- a/api-js/src/domain/mutations/__tests__/remove-domain.test.js +++ b/api/src/domain/mutations/__tests__/remove-domain.test.js @@ -1,18 +1,20 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' import { cleanseInput } from '../../../validators' -import { checkPermission, userRequired, verifiedRequired } from '../../../auth' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' import { loadDomainByKey } from '../../loaders' import { loadOrgByKey } from '../../../organization/loaders' import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -39,15 +41,20 @@ describe('removing a domain', () => { describe('given a successful domain removal', () => { beforeEach(async () => { ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) user = await collections.users.save({ userName: 'test.account@istio.actually.exists', emailValidated: true, + tfaSendMethod: 'email', }) }) afterEach(async () => { @@ -103,38 +110,27 @@ describe('removing a domain', () => { _from: org._id, _to: domain._id, }) - const dkim = await collections.dkim.save({ dkim: true }) - await collections.domainsDKIM.save({ - _from: domain._id, - _to: dkim._id, - }) - const dkimResult = await collections.dkimResults.save({ - dkimResult: true, - }) - await collections.dkimToDkimResults.save({ - _from: dkim._id, - _to: dkimResult._id, - }) - const dmarc = await collections.dmarc.save({ dmarc: true }) - await collections.domainsDMARC.save({ + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ _from: domain._id, - _to: dmarc._id, + _to: dns._id, }) - const spf = await collections.spf.save({ spf: true }) - await collections.domainsSPF.save({ + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ _from: domain._id, - _to: spf._id, + _to: web._id, }) - const https = await collections.https.save({ https: true }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, + + const webScan = await collections.webScan.save({ + webScan: true, }) - const ssl = await collections.ssl.save({ ssl: true }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, }) + const dmarcSummary = await collections.dmarcSummaries.save({ dmarcSummary: true, }) @@ -197,12 +193,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -222,13 +219,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -239,6 +237,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -247,13 +246,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: `Successfully removed domain: test-gc-ca from communications-security-establishment.`, + status: `Successfully removed domain: test.gc.ca from communications-security-establishment.`, domain: { domain: 'test.gc.ca', }, @@ -264,7 +263,7 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: communications-security-establishment.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: communications-security-establishment.`, ]) }) }) @@ -284,12 +283,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -309,13 +309,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -326,6 +327,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -334,14 +336,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: - 'A réussi à supprimer le domaine : test-gc-ca de communications-security-establishment.', + status: 'A réussi à supprimer le domaine : test.gc.ca de communications-security-establishment.', domain: { domain: 'test.gc.ca', }, @@ -352,17 +353,18 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: communications-security-establishment.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: communications-security-establishment.`, ]) }) }) it('does not remove domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -382,11 +384,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -399,6 +401,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -407,7 +410,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -419,12 +422,13 @@ describe('removing a domain', () => { expect(domainCheck._key).toEqual(domain._key) }) it('does not remove all scan data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -444,11 +448,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -461,6 +465,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -469,37 +474,20 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult.dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(true) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan.dkim` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(true) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan.dmarc` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(true) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan.spf` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(true) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan.https` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(true) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan.ssl` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(true) + }) + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(true) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(true) }) describe('org owns dmarc summary data', () => { beforeEach(async () => { @@ -509,12 +497,13 @@ describe('removing a domain', () => { }) }) it('removes dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -534,13 +523,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -551,6 +541,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -559,10 +550,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toEqual(undefined) @@ -573,8 +563,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toEqual(undefined) }) }) @@ -586,12 +575,13 @@ describe('removing a domain', () => { }) }) it('does not remove dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -611,11 +601,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -628,6 +618,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -636,10 +627,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toBeDefined() @@ -650,8 +640,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toBeDefined() }) }) @@ -704,12 +693,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -729,13 +719,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -746,6 +737,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -754,13 +746,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: `Successfully removed domain: test-gc-ca from communications-security-establishment.`, + status: `Successfully removed domain: test.gc.ca from communications-security-establishment.`, domain: { domain: 'test.gc.ca', }, @@ -771,7 +763,7 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: communications-security-establishment.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: communications-security-establishment.`, ]) }) }) @@ -791,12 +783,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -816,13 +809,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -833,6 +827,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -841,14 +836,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: - 'A réussi à supprimer le domaine : test-gc-ca de communications-security-establishment.', + status: 'A réussi à supprimer le domaine : test.gc.ca de communications-security-establishment.', domain: { domain: 'test.gc.ca', }, @@ -859,17 +853,18 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: communications-security-establishment.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: communications-security-establishment.`, ]) }) }) it('does not remove domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -889,11 +884,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -906,6 +901,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -914,7 +910,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -926,12 +922,13 @@ describe('removing a domain', () => { expect(domainCheck._key).toEqual(domain._key) }) it('does not remove all scan data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -951,11 +948,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -968,6 +965,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -976,37 +974,24 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult.dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(true) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan.dkim` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(true) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan.dmarc` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(true) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan.spf` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(true) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan.https` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(true) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan.ssl` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(true) + }) + + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(true) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(true) }) describe('org owns dmarc summary data', () => { beforeEach(async () => { @@ -1016,12 +1001,13 @@ describe('removing a domain', () => { }) }) it('removes dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -1041,13 +1027,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1058,6 +1045,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1066,10 +1054,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toEqual(undefined) @@ -1080,8 +1067,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toEqual(undefined) }) }) @@ -1093,12 +1079,13 @@ describe('removing a domain', () => { }) }) it('does not remove dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -1118,11 +1105,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1135,6 +1122,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1143,10 +1131,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toBeDefined() @@ -1157,8 +1144,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toBeDefined() }) }) @@ -1188,12 +1174,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1213,13 +1200,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1230,6 +1218,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1238,13 +1227,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: `Successfully removed domain: test-gc-ca from treasury-board-secretariat.`, + status: `Successfully removed domain: test.gc.ca from treasury-board-secretariat.`, domain: { domain: 'test.gc.ca', }, @@ -1255,7 +1244,7 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) @@ -1275,12 +1264,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1300,13 +1290,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1317,6 +1308,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1325,14 +1317,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: - 'A réussi à supprimer le domaine : test-gc-ca de treasury-board-secretariat.', + status: 'A réussi à supprimer le domaine : test.gc.ca de treasury-board-secretariat.', domain: { domain: 'test.gc.ca', }, @@ -1343,17 +1334,18 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) it('removes domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1373,13 +1365,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1390,6 +1383,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1398,7 +1392,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -1410,12 +1404,13 @@ describe('removing a domain', () => { expect(domainCheck).toEqual(undefined) }) it('removes all scan data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1435,13 +1430,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1452,6 +1448,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1460,73 +1457,23 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) - - await query` - FOR dkimResult IN dkimResults - OPTIONS { waitForSync: true } - RETURN dkimResult - ` - - await query` - FOR dkimScan IN dkim - OPTIONS { waitForSync: true } - RETURN dkimScan - ` - - await query` - FOR dmarcScan IN dmarc - OPTIONS { waitForSync: true } - RETURN dmarcScan - ` + }) - await query` - FOR spfScan IN spf - OPTIONS { waitForSync: true } - RETURN spfScan - ` + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - await query` - FOR httpsScan IN https - OPTIONS { waitForSync: true } - RETURN httpsScan - ` + const testWebScanCursor = await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(undefined) - await query` - FOR sslScan IN ssl - OPTIONS { waitForSync: true } - RETURN sslScan - ` + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) }) describe('org owns dmarc summary data', () => { beforeEach(async () => { @@ -1536,12 +1483,13 @@ describe('removing a domain', () => { }) }) it('removes dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1561,13 +1509,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1578,6 +1527,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1586,10 +1536,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toEqual(undefined) @@ -1600,8 +1549,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toEqual(undefined) }) }) @@ -1613,12 +1561,13 @@ describe('removing a domain', () => { }) }) it('does not remove dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -1638,11 +1587,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -1655,6 +1604,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1663,10 +1613,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toBeDefined() @@ -1677,8 +1626,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toBeDefined() }) }) @@ -1700,12 +1648,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1725,13 +1674,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1742,6 +1692,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1750,13 +1701,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: `Successfully removed domain: test-gc-ca from treasury-board-secretariat.`, + status: `Successfully removed domain: test.gc.ca from treasury-board-secretariat.`, domain: { domain: 'test.gc.ca', }, @@ -1767,7 +1718,7 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) @@ -1787,12 +1738,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1812,13 +1764,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1829,6 +1782,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1837,14 +1791,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: - 'A réussi à supprimer le domaine : test-gc-ca de treasury-board-secretariat.', + status: 'A réussi à supprimer le domaine : test.gc.ca de treasury-board-secretariat.', domain: { domain: 'test.gc.ca', }, @@ -1855,17 +1808,18 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) it('removes domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1885,13 +1839,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1902,6 +1857,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1910,7 +1866,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -1922,12 +1878,13 @@ describe('removing a domain', () => { expect(domainCheck).toEqual(undefined) }) it('removes all scan data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1947,13 +1904,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1964,6 +1922,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -1972,73 +1931,23 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) - - await query` - FOR dkimResult IN dkimResults - OPTIONS { waitForSync: true } - RETURN dkimResult - ` - - await query` - FOR dkimScan IN dkim - OPTIONS { waitForSync: true } - RETURN dkimScan - ` + }) - await query` - FOR dmarcScan IN dmarc - OPTIONS { waitForSync: true } - RETURN dmarcScan - ` + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - await query` - FOR spfScan IN spf - OPTIONS { waitForSync: true } - RETURN spfScan - ` + const testWebScanCursor = await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(undefined) - await query` - FOR httpsScan IN https - OPTIONS { waitForSync: true } - RETURN httpsScan - ` - - await query` - FOR sslScan IN ssl - OPTIONS { waitForSync: true } - RETURN sslScan - ` + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) }) describe('org owns dmarc summary data', () => { beforeEach(async () => { @@ -2048,12 +1957,13 @@ describe('removing a domain', () => { }) }) it('removes dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2073,13 +1983,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2090,6 +2001,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2098,10 +2010,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toEqual(undefined) @@ -2112,8 +2023,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toEqual(undefined) }) }) @@ -2125,12 +2035,13 @@ describe('removing a domain', () => { }) }) it('does not remove dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -2150,11 +2061,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -2167,6 +2078,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2175,10 +2087,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toBeDefined() @@ -2189,8 +2100,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toBeDefined() }) }) @@ -2233,38 +2143,27 @@ describe('removing a domain', () => { _from: org._id, _to: domain._id, }) - const dkim = await collections.dkim.save({ dkim: true }) - await collections.domainsDKIM.save({ - _from: domain._id, - _to: dkim._id, - }) - const dkimResult = await collections.dkimResults.save({ - dkimResult: true, - }) - await collections.dkimToDkimResults.save({ - _from: dkim._id, - _to: dkimResult._id, - }) - const dmarc = await collections.dmarc.save({ dmarc: true }) - await collections.domainsDMARC.save({ + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ _from: domain._id, - _to: dmarc._id, + _to: dns._id, }) - const spf = await collections.spf.save({ spf: true }) - await collections.domainsSPF.save({ + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ _from: domain._id, - _to: spf._id, + _to: web._id, }) - const https = await collections.https.save({ https: true }) - await collections.domainsHTTPS.save({ - _from: domain._id, - _to: https._id, + + const webScan = await collections.webScan.save({ + webScan: true, }) - const ssl = await collections.ssl.save({ ssl: true }) - await collections.domainsSSL.save({ - _from: domain._id, - _to: ssl._id, + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, }) + await collections.affiliations.save({ _from: org._id, _to: user._id, @@ -2328,12 +2227,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2353,13 +2253,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2370,6 +2271,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2378,13 +2280,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: `Successfully removed domain: test-gc-ca from treasury-board-secretariat.`, + status: `Successfully removed domain: test.gc.ca from treasury-board-secretariat.`, domain: { domain: 'test.gc.ca', }, @@ -2395,7 +2297,7 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) @@ -2415,12 +2317,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2440,13 +2343,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2457,6 +2361,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2465,14 +2370,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: - 'A réussi à supprimer le domaine : test-gc-ca de treasury-board-secretariat.', + status: 'A réussi à supprimer le domaine : test.gc.ca de treasury-board-secretariat.', domain: { domain: 'test.gc.ca', }, @@ -2483,17 +2387,18 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) it('does not remove domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2513,11 +2418,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -2530,6 +2435,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2538,7 +2444,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -2550,12 +2456,13 @@ describe('removing a domain', () => { expect(domainCheck._key).toEqual(domain._key) }) it('does not remove all scan data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2575,11 +2482,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -2592,6 +2499,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2600,37 +2508,20 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) - - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult.dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(true) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan.dkim` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(true) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan.dmarc` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(true) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan.spf` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(true) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan.https` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(true) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan.ssl` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(true) + }) + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(true) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(true) }) describe('org owns dmarc summary data', () => { beforeEach(async () => { @@ -2640,12 +2531,13 @@ describe('removing a domain', () => { }) }) it('removes dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2665,13 +2557,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2682,6 +2575,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2690,10 +2584,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toEqual(undefined) @@ -2704,8 +2597,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toEqual(undefined) }) }) @@ -2717,12 +2609,13 @@ describe('removing a domain', () => { }) }) it('does not remove dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -2742,11 +2635,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -2759,6 +2652,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2767,10 +2661,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toBeDefined() @@ -2781,8 +2674,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toBeDefined() }) }) @@ -2806,12 +2698,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2831,13 +2724,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2848,6 +2742,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2856,13 +2751,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: `Successfully removed domain: test-gc-ca from treasury-board-secretariat.`, + status: `Successfully removed domain: test.gc.ca from treasury-board-secretariat.`, domain: { domain: 'test.gc.ca', }, @@ -2873,7 +2768,7 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) @@ -2893,12 +2788,13 @@ describe('removing a domain', () => { }) }) it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2918,13 +2814,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2935,6 +2832,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -2943,14 +2841,13 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { removeDomain: { result: { - status: - 'A réussi à supprimer le domaine : test-gc-ca de treasury-board-secretariat.', + status: 'A réussi à supprimer le domaine : test.gc.ca de treasury-board-secretariat.', domain: { domain: 'test.gc.ca', }, @@ -2961,17 +2858,18 @@ describe('removing a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed domain: test-gc-ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, ]) }) }) it('removes domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2991,13 +2889,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3008,6 +2907,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -3016,7 +2916,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -3028,12 +2928,13 @@ describe('removing a domain', () => { expect(domainCheck).toEqual(undefined) }) it('removes all scan data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -3053,13 +2954,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3070,6 +2972,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -3078,73 +2981,19 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) - - await query` - FOR dkimResult IN dkimResults - OPTIONS { waitForSync: true } - RETURN dkimResult - ` - - await query` - FOR dkimScan IN dkim - OPTIONS { waitForSync: true } - RETURN dkimScan - ` - - await query` - FOR dmarcScan IN dmarc - OPTIONS { waitForSync: true } - RETURN dmarcScan - ` - - await query` - FOR spfScan IN spf - OPTIONS { waitForSync: true } - RETURN spfScan - ` + }) - await query` - FOR httpsScan IN https - OPTIONS { waitForSync: true } - RETURN httpsScan - ` + const testWebScanCursor = await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(undefined) - await query` - FOR sslScan IN ssl - OPTIONS { waitForSync: true } - RETURN sslScan - ` + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) - - const testDkimCursor = - await query`FOR dkimScan IN dkim OPTIONS { waitForSync: true } RETURN dkimScan` - const testDkim = await testDkimCursor.next() - expect(testDkim).toEqual(undefined) - - const testDmarcCursor = - await query`FOR dmarcScan IN dmarc OPTIONS { waitForSync: true } RETURN dmarcScan` - const testDmarc = await testDmarcCursor.next() - expect(testDmarc).toEqual(undefined) - - const testSpfCursor = - await query`FOR spfScan IN spf OPTIONS { waitForSync: true } RETURN spfScan` - const testSpf = await testSpfCursor.next() - expect(testSpf).toEqual(undefined) - - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) - - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toEqual(undefined) + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) }) describe('org owns dmarc summary data', () => { beforeEach(async () => { @@ -3154,12 +3003,13 @@ describe('removing a domain', () => { }) }) it('removes dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -3179,13 +3029,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3196,6 +3047,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -3204,10 +3056,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toEqual(undefined) @@ -3218,8 +3069,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toEqual(undefined) }) }) @@ -3231,12 +3081,13 @@ describe('removing a domain', () => { }) }) it('does not remove dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', secondOrg._key)}" } @@ -3256,11 +3107,11 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, auth: { @@ -3273,6 +3124,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, validators: { cleanseInput }, loaders: { @@ -3281,10 +3133,9 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` const testOwnership = await testOwnershipCursor.next() expect(testOwnership).toBeDefined() @@ -3295,8 +3146,7 @@ describe('removing a domain', () => { const testDomainsToDmarcSumCursor = await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() expect(testDomainsToDmarcSum).toBeDefined() }) }) @@ -3322,12 +3172,13 @@ describe('removing a domain', () => { }) describe('domain does not exist', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 1)}" orgId: "${toGlobalId('organization', 1)}" } @@ -3347,17 +3198,19 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { @@ -3368,7 +3221,7 @@ describe('removing a domain', () => { loadUserByKey: jest.fn(), }, }, - ) + }) const error = { data: { @@ -3389,12 +3242,13 @@ describe('removing a domain', () => { }) describe('organization does not exist', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 1)}" } @@ -3414,17 +3268,19 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { @@ -3437,15 +3293,14 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 400, - description: - 'Unable to remove domain from unknown organization.', + description: 'Unable to remove domain from unknown organization.', }, }, }, @@ -3460,12 +3315,13 @@ describe('removing a domain', () => { describe('user attempts to remove domain from verified check org', () => { describe('users permission is admin', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3485,23 +3341,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3512,15 +3370,14 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 403, - description: - 'Permission Denied: Please contact super admin for help with removing domain.', + description: 'Permission Denied: Please contact super admin for help with removing domain.', }, }, }, @@ -3528,18 +3385,19 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org but does not have permission to remove a domain from a verified check org.`, ]) }) }) describe('users permission is user', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3559,23 +3417,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3586,15 +3446,14 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 403, - description: - 'Permission Denied: Please contact super admin for help with removing domain.', + description: 'Permission Denied: Please contact organization admin for help with removing domain.', }, }, }, @@ -3602,18 +3461,19 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) describe('user does not belong to org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3633,23 +3493,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3660,15 +3522,14 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 403, - description: - 'Permission Denied: Please contact super admin for help with removing domain.', + description: 'Permission Denied: Please contact organization admin for help with removing domain.', }, }, }, @@ -3676,7 +3537,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -3684,12 +3545,13 @@ describe('removing a domain', () => { describe('user attempts to remove domain from a regular org', () => { describe('users permission is user', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3709,23 +3571,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3736,15 +3600,14 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 403, - description: - 'Permission Denied: Please contact organization admin for help with removing domain.', + description: 'Permission Denied: Please contact organization admin for help with removing domain.', }, }, }, @@ -3752,18 +3615,19 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org however they do not have permission in that org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) describe('user does not belong to org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3783,23 +3647,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3810,15 +3676,14 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 403, - description: - 'Permission Denied: Please contact organization admin for help with removing domain.', + description: 'Permission Denied: Please contact organization admin for help with removing domain.', }, }, }, @@ -3826,7 +3691,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org however they do not have permission in that org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -3834,12 +3699,13 @@ describe('removing a domain', () => { describe('database error occurs', () => { describe('when checking to see how many edges there are', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3859,23 +3725,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('database error')), - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3886,26 +3754,25 @@ describe('removing a domain', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Database error occurred for user: 123, when counting domain claims for domain: domain-gc-ca, error: Error: database error`, + `Database error occurred for user: 123, when counting domain claims for domain: domain.gc.ca, error: Error: database error`, ]) }) }) describe('when checking to see if domain has dmarc summary data', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -3925,45 +3792,49 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest .fn() - .mockReturnValueOnce({}) + .mockReturnValueOnce({ + all: jest.fn().mockReturnValueOnce([{ _id: toGlobalId('organization', 456) }]), + }) .mockRejectedValue(new Error('database error')), - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + _id: toGlobalId('domains', 123), + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organization', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Database error occurred for user: 123, when counting ownership claims for domain: domain-gc-ca, error: Error: database error`, + `Database error occurred for user: 123, when counting ownership claims for domain: domain.gc.ca, error: Error: database error`, ]) }) }) @@ -3974,14 +3845,16 @@ describe('removing a domain', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4001,60 +3874,64 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, + query: jest.fn().mockReturnValue({ + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organization', 456) }]), + count: 1, + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + _id: toGlobalId('domains', 123), + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organization', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dmarc summary data for user: 123 while attempting to remove domain: domain-gc-ca, error: Error: trx step error`, + `Trx step error occurred when removing dmarc summary data for user: 123 while attempting to remove domain: domain.gc.ca, error: Error: trx step error`, ]) }) }) describe('when removing ownership info', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4074,42 +3951,47 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, + query: jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organization', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + _id: toGlobalId('domains', 123), + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organization', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownership data for user: 123 while attempting to remove domain: domain-gc-ca, error: Error: trx step error`, + `Trx step error occurred when removing ownership data for user: 123 while attempting to remove domain: domain.gc.ca, error: Error: trx step error`, ]) }) }) @@ -4122,14 +4004,16 @@ describe('removing a domain', () => { .mockReturnValueOnce() .mockReturnValueOnce() .mockRejectedValue(new Error('Transaction error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4149,45 +4033,50 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest .fn() - .mockReturnValueOnce({ count: 0 }) + .mockReturnValueOnce({ + count: 0, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organization', 456) }]), + }) .mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + _id: toGlobalId('domains', 123), + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organization', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove scan data for domain-gc-ca in org: temp-org, error: Error: Transaction error occurred.`, + `Trx step error occurred while user: 123 attempted to remove web data for domain.gc.ca in org: temp-org, error: Error: Transaction error occurred.`, ]) }) }) @@ -4203,17 +4092,17 @@ describe('removing a domain', () => { .mockReturnValueOnce() .mockReturnValueOnce() .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() .mockRejectedValue(new Error('Step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4233,63 +4122,69 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, + query: jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + _id: toGlobalId('domains', 123), + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove domain-gc-ca in org: temp-org, error: Error: Step error`, + `Trx step error occurred while user: 123 attempted to remove domain domain.gc.ca in org: temp-org, error: Error: Step error`, ]) }) }) describe('domain has more than one edge', () => { it('returns an error', async () => { - const cursor = { + const mockedQuery = jest.fn().mockReturnValue({ count: 2, - } - - const mockedQuery = jest.fn().mockReturnValue(cursor) + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }) const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('Step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4309,42 +4204,43 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove claim for domain-gc-ca in org: temp-org, error: Error: Step error`, + `Trx step error occurred while user: 123 attempted to remove claim for domain.gc.ca in org: temp-org, error: Error: Step error`, ]) }) }) @@ -4354,17 +4250,17 @@ describe('removing a domain', () => { it('returns an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction error occurred.')), + commit: jest.fn().mockRejectedValue(new Error('Transaction error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4384,42 +4280,46 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 2 }), - collections, + query: jest.fn().mockReturnValue({ + count: 2, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to remove domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to remove domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx commit error occurred while user: 123 attempted to remove domain-gc-ca in org: temp-org, error: Error: Transaction error occurred.`, + `Trx commit error occurred while user: 123 attempted to remove domain.gc.ca in org: temp-org, error: Error: Transaction error occurred.`, ]) }) }) @@ -4441,12 +4341,13 @@ describe('removing a domain', () => { }) describe('domain does not exist', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 1)}" orgId: "${toGlobalId('organization', 1)}" } @@ -4466,17 +4367,19 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { @@ -4487,7 +4390,7 @@ describe('removing a domain', () => { loadUserByKey: jest.fn(), }, }, - ) + }) const error = { data: { @@ -4508,12 +4411,13 @@ describe('removing a domain', () => { }) describe('organization does not exist', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 1)}" } @@ -4533,17 +4437,19 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { @@ -4556,15 +4462,14 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { removeDomain: { result: { code: 400, - description: - "Impossible de supprimer le domaine d'une organisation inconnue.", + description: "Impossible de supprimer le domaine d'une organisation inconnue.", }, }, }, @@ -4579,12 +4484,13 @@ describe('removing a domain', () => { describe('user attempts to remove domain from verified check org', () => { describe('users permission is admin', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4604,23 +4510,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4631,7 +4539,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4647,18 +4555,19 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org but does not have permission to remove a domain from a verified check org.`, ]) }) }) describe('users permission is user', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4678,23 +4587,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4705,7 +4616,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4713,7 +4624,7 @@ describe('removing a domain', () => { result: { code: 403, description: - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.", }, }, }, @@ -4721,18 +4632,19 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) describe('user does not belong to org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4752,23 +4664,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4779,7 +4693,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4787,7 +4701,7 @@ describe('removing a domain', () => { result: { code: 403, description: - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.", }, }, }, @@ -4795,7 +4709,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -4803,12 +4717,13 @@ describe('removing a domain', () => { describe('user attempts to remove domain from a regular org', () => { describe('users permission is user', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4828,23 +4743,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4855,7 +4772,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4871,18 +4788,19 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org however they do not have permission in that org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) describe('user does not belong to org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4902,23 +4820,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4929,7 +4849,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4945,7 +4865,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain-gc-ca in temp-org however they do not have permission in that org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -4953,12 +4873,13 @@ describe('removing a domain', () => { describe('database error occurs', () => { describe('when checking to see how many edges there are', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -4978,23 +4899,25 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('database error')), - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -5005,28 +4928,25 @@ describe('removing a domain', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Database error occurred for user: 123, when counting domain claims for domain: domain-gc-ca, error: Error: database error`, + `Database error occurred for user: 123, when counting domain claims for domain: domain.gc.ca, error: Error: database error`, ]) }) }) describe('when checking to see if domain has dmarc summary data', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5046,47 +4966,48 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest .fn() - .mockReturnValueOnce({}) + .mockReturnValueOnce({ + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }) .mockRejectedValue(new Error('database error')), - collections, + collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Database error occurred for user: 123, when counting ownership claims for domain: domain-gc-ca, error: Error: database error`, + `Database error occurred for user: 123, when counting ownership claims for domain: domain.gc.ca, error: Error: database error`, ]) }) }) @@ -5097,14 +5018,16 @@ describe('removing a domain', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5124,62 +5047,63 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, + query: jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dmarc summary data for user: 123 while attempting to remove domain: domain-gc-ca, error: Error: trx step error`, + `Trx step error occurred when removing dmarc summary data for user: 123 while attempting to remove domain: domain.gc.ca, error: Error: trx step error`, ]) }) }) describe('when removing ownership info', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5199,44 +5123,46 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, + query: jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownership data for user: 123 while attempting to remove domain: domain-gc-ca, error: Error: trx step error`, + `Trx step error occurred when removing ownership data for user: 123 while attempting to remove domain: domain.gc.ca, error: Error: trx step error`, ]) }) }) @@ -5249,14 +5175,16 @@ describe('removing a domain', () => { .mockReturnValueOnce() .mockReturnValueOnce() .mockRejectedValue(new Error('Transaction error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5276,47 +5204,49 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest .fn() - .mockReturnValueOnce({ count: 0 }) + .mockReturnValueOnce({ + count: 0, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }) .mockReturnValue({ count: 1 }), - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove scan data for domain-gc-ca in org: temp-org, error: Error: Transaction error occurred.`, + `Trx step error occurred while user: 123 attempted to remove web data for domain.gc.ca in org: temp-org, error: Error: Transaction error occurred.`, ]) }) }) @@ -5332,17 +5262,17 @@ describe('removing a domain', () => { .mockReturnValueOnce() .mockReturnValueOnce() .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() .mockRejectedValue(new Error('Step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5362,44 +5292,46 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), - collections, + query: jest.fn().mockReturnValue({ + count: 1, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove domain-gc-ca in org: temp-org, error: Error: Step error`, + `Trx step error occurred while user: 123 attempted to remove domain domain.gc.ca in org: temp-org, error: Error: Step error`, ]) }) }) @@ -5407,20 +5339,23 @@ describe('removing a domain', () => { it('returns an error', async () => { const cursor = { count: 2, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), } const mockedQuery = jest.fn().mockReturnValue(cursor) const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockRejectedValue(new Error('Step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5440,44 +5375,43 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, - collections, + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove claim for domain-gc-ca in org: temp-org, error: Error: Step error`, + `Trx step error occurred while user: 123 attempted to remove claim for domain.gc.ca in org: temp-org, error: Error: Step error`, ]) }) }) @@ -5487,17 +5421,17 @@ describe('removing a domain', () => { it('returns an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction error occurred.')), + commit: jest.fn().mockRejectedValue(new Error('Transaction error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 456)}" } @@ -5517,44 +5451,46 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 2 }), - collections, + query: jest.fn().mockReturnValue({ + count: 2, + all: jest.fn().mockReturnValue([{ _id: toGlobalId('organizations', 456) }]), + }), + collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), }, validators: { cleanseInput }, loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { load: jest.fn().mockReturnValue({ + _id: toGlobalId('organizations', 456), verified: false, slug: 'temp-org', }), }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx commit error occurred while user: 123 attempted to remove domain-gc-ca in org: temp-org, error: Error: Transaction error occurred.`, + `Trx commit error occurred while user: 123 attempted to remove domain.gc.ca in org: temp-org, error: Error: Transaction error occurred.`, ]) }) }) diff --git a/api/src/domain/mutations/__tests__/remove-organizations-domains.test.js b/api/src/domain/mutations/__tests__/remove-organizations-domains.test.js new file mode 100644 index 0000000000..cf385dc7cc --- /dev/null +++ b/api/src/domain/mutations/__tests__/remove-organizations-domains.test.js @@ -0,0 +1,658 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' +import { loadDomainByDomain } from '../../loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the addOrganizationsDomains mutation', () => { + let query, drop, i18n, truncate, schema, collections, transaction, user, org, domain, domain2, org2 + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful bulk domain removal', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + org2 = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'test-org', + acronym: 'TO', + name: 'Test Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('user has super admin permission level', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + archived: false, + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, + }) + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, + }) + + const webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) + + const dmarcSummary = await collections.dmarcSummaries.save({ + dmarcSummary: true, + }) + await collections.domainsToDmarcSummaries.save({ + _from: domain._id, + _to: dmarcSummary._id, + }) + + domain2 = await collections.domains.save({ + domain: 'test2.gc.ca', + slug: 'test2-gc-ca', + archived: false, + }) + await collections.claims.save({ + _from: org._id, + _to: domain2._id, + }) + await collections.claims.save({ + _from: org2._id, + _to: domain2._id, + }) + + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it.skip('removes domains', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganizationsDomains( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domains: ["test.gc.ca", "test2.gc.ca"] + archiveDomains: false + audit: false + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + removeOrganizationsDomains: { + result: { + status: `Successfully removed 2 domain(s) from treasury-board-secretariat.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully removed 2 domain(s) from org: treasury-board-secretariat.`, + ]) + }) + it.skip(`"audit" flag is true`, async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganizationsDomains( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domains: ["test.gc.ca", "test2.gc.ca"] + archiveDomains: false + audit: true + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + removeOrganizationsDomains: { + result: { + status: `Successfully removed 2 domain(s) from treasury-board-secretariat.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully removed domain: test.gc.ca from org: treasury-board-secretariat.`, + `User: ${user._key} successfully removed domain: test2.gc.ca from org: treasury-board-secretariat.`, + ]) + }) + it.skip(`"archive" flag is true`, async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganizationsDomains( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domains: ["test.gc.ca", "test2.gc.ca"] + archiveDomains: true + audit: false + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + removeOrganizationsDomains: { + result: { + status: `Successfully removed 2 domain(s) from treasury-board-secretariat.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully removed 2 domain(s) from org: treasury-board-secretariat.`, + ]) + }) + }) + }) + + describe('given an unsuccessful bulk domain creation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + org2 = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'test-org', + acronym: 'TO', + name: 'Test Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('user has admin permission level', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + archived: false, + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + + domain2 = await collections.domains.save({ + domain: 'test2.gc.ca', + slug: 'test2-gc-ca', + archived: false, + }) + await collections.claims.save({ + _from: org._id, + _to: domain2._id, + }) + await collections.claims.save({ + _from: org2._id, + _to: domain2._id, + }) + + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + await collections.affiliations.save({ + _from: org2._id, + _to: user._id, + permission: 'admin', + }) + }) + describe('org is verified', () => { + it.skip('returns error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganizationsDomains( + input: { + orgId: "${toGlobalId('organization', org._key)}" + domains: ["test.gc.ca", "test2.gc.ca"] + archiveDomains: false + audit: false + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + removeOrganizationsDomains: { + result: { + code: 403, + description: `Permission Denied: Please contact super admin for help with removing domain.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + describe('archive flag is true', () => { + it.skip('returns error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganizationsDomains( + input: { + orgId: "${toGlobalId('organization', org2._key)}" + domains: ["test2.gc.ca"] + archiveDomains: true + audit: false + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + publish: jest.fn(), + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + const expectedResponse = { + data: { + removeOrganizationsDomains: { + result: { + code: 403, + description: `Permission Denied: Please contact organization admin for help with archiving domains.`, + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/unfavourite-domain.test.js b/api/src/domain/mutations/__tests__/unfavourite-domain.test.js new file mode 100644 index 0000000000..a55c50e47d --- /dev/null +++ b/api/src/domain/mutations/__tests__/unfavourite-domain.test.js @@ -0,0 +1,153 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput } from '../../../validators' +import { userRequired, verifiedRequired } from '../../../auth' +import { loadDomainByKey } from '../../loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env + +describe('favourite a domain', () => { + let query, drop, i18n, truncate, schema, collections, transaction, user, domain1, favourite1 + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful domain creation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + afterEach(async () => { + await truncate() + await drop() + }) + describe('user is logged in and verified', () => { + beforeEach(async () => { + domain1 = await collections.domains.save({ + domain: 'test.gc.ca', + }) + favourite1 = await collections.favourites.save({ + _to: domain1._id, + _from: user._id, + }) + }) + describe('user unfavourites a domain', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns the success status', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + unfavouriteDomain(input: { domainId: "${toGlobalId('domain', domain1._key)}" }) { + result { + ... on DomainResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + loaders: { + loadDomainByKey: loadDomainByKey({ + query, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + unfavouriteDomain: { + result: { + status: 'Successfully removed domain: test.gc.ca from favourites.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(favourite1).not.toEqual(null) + + expect(consoleOutput).toEqual([`User: ${user._key} successfully removed domain test.gc.ca from favourites.`]) + }) + }) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/unignore-cve.test.js b/api/src/domain/mutations/__tests__/unignore-cve.test.js new file mode 100644 index 0000000000..78e9d3c7ff --- /dev/null +++ b/api/src/domain/mutations/__tests__/unignore-cve.test.js @@ -0,0 +1,353 @@ +import { graphql, GraphQLError, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { createMutationSchema } from '../../../mutation' +import { ensureDatabase as ensure, createUserContextGenerator } from '../../../testUtilities' +import { dbNameFromFile } from 'arango-tools' +import dbschema from '../../../../../database-migration/database.json' +import { createQuerySchema } from '../../../query' +import { createI18n } from '../../../create-i18n' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, AUTHENTICATED_KEY, HASHING_SALT } = process.env + +const schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), +}) +const consoleOutput = [] +const mockedInfo = (output) => consoleOutput.push(output) +const mockedWarn = (output) => consoleOutput.push(output) +const mockedError = (output) => consoleOutput.push(output) +console.info = mockedInfo +console.warn = mockedWarn +console.error = mockedError + +const i18n = createI18n('en') + +let db, + query, + drop, + truncate, + collections, + transaction, + createUserContext, + domain, + superAdminUser, + superAdminContext, + normalUser, + normalUserContext + +const cve = 'CVE-1234-55555' + +describe('unignore mutation', () => { + beforeAll(async () => { + ;({ db, query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + + createUserContext = createUserContextGenerator({ + query, + db, + transaction, + collectionNames, + i18n, + secret: AUTHENTICATED_KEY, + salt: HASHING_SALT, + }) + }) + + beforeEach(async () => { + superAdminUser = ( + await collections.users.save( + { + _key: 'superadminuser', + userName: 'sueradminuser@test.gc.ca', + emailValidated: true, + }, + { returnNew: true }, + ) + ).new + superAdminContext = await createUserContext({ userKey: superAdminUser._key }) + normalUser = ( + await collections.users.save( + { + _key: 'normaluser', + userName: 'normaluser@test.gc.ca', + emailValidated: true, + }, + { returnNew: true }, + ) + ).new + normalUserContext = await createUserContext({ userKey: normalUser._key }) + + const superAdminOrg = ( + await collections.organizations.save( + { + _key: 'superadminorg', + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + }, + }, + }, + { returnNew: true }, + ) + ).new + await collections.affiliations.save( + { + _from: superAdminOrg._id, + _to: superAdminUser._id, + permission: 'super_admin', + }, + { returnNew: true }, + ) + + domain = ( + await collections.domains.save( + { + _key: '123', + domain: 'test.domain.gc.ca', + slug: 'test-domain-gc-ca', + ignoredCves: [cve], + }, + { returnNew: true }, + ) + ).new + }) + + afterEach(async () => { + consoleOutput.length = 0 + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + it('returns an error when the user is not a super admin', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + unignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: normalUserContext, + }) + + const expectedError = 'Permissions error. You do not have sufficient permissions to access this data.' + expect(response.errors[0].message).toEqual(expectedError) + + const expectConsoleOutput = [ + `User: ${normalUser._key} attempted to access controlled functionality without sufficient privileges.`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + describe('given a super admin user', () => { + it('returns an error when the domain does not exist', async () => { + const badDomainKey = 'bad-domain-key' + const response = await graphql({ + schema, + source: ` + mutation { + unignoreCve(input: { domainId: "${toGlobalId('domain', badDomainKey)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const expectedError = { + code: 400, + description: 'Unable to stop ignoring CVE. Please try again.', + } + expect(response.data.unignoreCve.result).toEqual(expectedError) + + const expectConsoleOutput = [ + `User: "${superAdminUser._key}" attempted to unignore CVE "${cve}" on unknown domain: "${badDomainKey}".`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('returns an error when the CVE is not already ignored', async () => { + // Remove the CVE from the domain's ignoredCves + await query` + UPDATE { + _key: ${domain._key}, + ignoredCves: [] + } IN domains + ` + // Ensure the CVE is not already ignored + const currentDomainState = await (await query`RETURN DOCUMENT(domains, ${domain._key}).ignoredCves || []`).next() + expect(currentDomainState).toEqual([]) + + const response = await graphql({ + schema, + source: ` + mutation { + unignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const expectedError = { + code: 400, + description: 'CVE is not ignored for this domain.', + } + expect(response.data.unignoreCve.result).toEqual(expectedError) + + const expectConsoleOutput = [ + `User: "${superAdminUser._key}" attempted to unignore CVE "${cve}" on domain: "${domain._key}" however CVE is not ignored.`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('throws an error when the transaction step fails', async () => { + superAdminContext.transaction = jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + unignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const error = [new GraphQLError('Unable to stop ignoring CVE. Please try again.')] + expect(response.errors).toEqual(error) + + const expectConsoleOutput = [ + `Transaction step error occurred when user: "${superAdminUser._key}" attempted to unignore CVE "${cve}" on domain "${domain._key}", error: Error: Transaction step error`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('throws an error when the transaction commit fails', async () => { + superAdminContext.transaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + unignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on DomainError { + code + description + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const error = [new GraphQLError('Unable to stop ignoring CVE. Please try again.')] + expect(response.errors).toEqual(error) + + const expectConsoleOutput = [ + `Transaction commit error occurred when user: "${superAdminUser._key}" attempted to unignore CVE "${cve}" on domain "${domain._key}", error: Error: Transaction commit error`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + + it('successfully unignores a CVE', async () => { + // Ensure CSV is ignored + const currentDomainState = await (await query`RETURN DOCUMENT(domains, ${domain._key}).ignoredCves || []`).next() + expect(currentDomainState).toEqual([cve]) + + const response = await graphql({ + schema, + source: ` + mutation { + unignoreCve(input: { domainId: "${toGlobalId('domain', domain._key)}", ignoredCve: "${cve}" }) { + result { + ... on Domain { + domain + ignoredCves + } + } + } + }`, + rootValue: null, + contextValue: superAdminContext, + }) + + const expectedResponse = { + data: { + unignoreCve: { + result: { + domain: domain.domain, + ignoredCves: [], + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + const domainCursor = await query` + FOR domain IN domains + FILTER domain._key == ${domain._key} + RETURN domain + ` + const domainArr = await domainCursor.all() + const domainObj = domainArr[0] + expect(domainObj.ignoredCves).toEqual([]) + + const expectConsoleOutput = [ + `User: "${superAdminUser._key}" successfully unignored CVE "${cve}" on domain: "${domain._key}".`, + ] + expect(consoleOutput).toEqual(expectConsoleOutput) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/update-domain.test.js b/api/src/domain/mutations/__tests__/update-domain.test.js new file mode 100644 index 0000000000..f5e26e75ec --- /dev/null +++ b/api/src/domain/mutations/__tests__/update-domain.test.js @@ -0,0 +1,1595 @@ +import { setupI18n } from '@lingui/core' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput, slugify } from '../../../validators' +import { + checkPermission, + userRequired, + verifiedRequired, + tfaRequired, + checkDomainPermission, + AuthDataSource, +} from '../../../auth' +import { loadDkimSelectorsByDomainId, loadDomainByKey } from '../../loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('updating a domain', () => { + let query, drop, truncate, schema, collections, transaction, publish, user + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful domain update', () => { + let org, domain + const i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + publish = jest.fn() + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + lastRan: null, + selectors: [], + }) + const selector1 = await collections.selectors.save({ selector: 'selector1' }) + const selector2 = await collections.selectors.save({ selector: 'selector2' }) + await collections.domainsToSelectors.save({ + _from: domain._id, + _to: selector1._id, + }) + await collections.domainsToSelectors.save({ + _from: domain._id, + _to: selector2._id, + }) + await collections.claims.save({ + _to: domain._id, + _from: org._id, + tags: [], + assetState: 'monitor-only', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users permission is super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _to: user._id, + _from: org._id, + permission: 'super_admin', + }) + }) + describe('user updates domain', () => { + it('returns updated domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', domain._key)}" + orgId: "${toGlobalId('organization', org._key)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + assetState + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + publish, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const expectedResponse = { + data: { + updateDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: null, + selectors: ['selector1', 'selector2'], + assetState: 'APPROVED', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) + }) + }) + }) + describe('users permission is admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _to: user._id, + _from: org._id, + permission: 'admin', + }) + }) + describe('user updates domain', () => { + it('returns updated domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', domain._key)}" + orgId: "${toGlobalId('organization', org._key)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + assetState + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + publish, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const expectedResponse = { + data: { + updateDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: null, + selectors: ['selector1', 'selector2'], + assetState: 'APPROVED', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) + }) + }) + }) + describe('users permission is user', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _to: user._id, + _from: org._id, + permission: 'admin', + }) + }) + describe('user updates domain', () => { + it('returns updated domain', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', domain._key)}" + orgId: "${toGlobalId('organization', org._key)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + assetState + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + publish, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const expectedResponse = { + data: { + updateDomain: { + result: { + id: toGlobalId('domain', domain._key), + domain: 'test.gc.ca', + lastRan: null, + selectors: ['selector1', 'selector2'], + assetState: 'APPROVED', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) + }) + }) + }) + }) + describe('given an unsuccessful domain update', () => { + let i18n + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + publish = jest.fn() + }) + describe('domain cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 1)}" + orgId: "${toGlobalId('organization', 1)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + assetState + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn(), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 400, + description: 'Unable to update unknown domain.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 1, however there is no domain associated with that id.`, + ]) + }) + }) + describe('organization cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 1)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + assetState + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 400, + description: 'Unable to update domain in an unknown org.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 123 for org: 1, however there is no org associated with that id.`, + ]) + }) + }) + describe('user does not belong to org', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue(undefined), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 403, + description: + 'Permission Denied: Please contact organization user for help with updating this domain.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 123 for org: 123, however they do not have permission in that org.`, + ]) + }) + }) + describe('domain and org do not have any edges', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ count: 0 }), + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 400, + description: 'Unable to update domain that does not belong to the given organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 123 for org: 123, however that org has no claims to that domain.`, + ]) + }) + }) + describe('database error occurs', () => { + describe('while checking for edge connections', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockRejectedValue(new Error('database error')), + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = [new GraphQLError('Unable to update domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Database error occurred while user: 123 attempted to update domain: 123, error: Error: database error`, + ]) + }) + }) + }) + describe('transaction step error occurs', () => { + describe('when running domain upsert', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ count: 1 }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), + }), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = [new GraphQLError('Unable to update domain. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred when user: 123 attempted to update domain: 123, error: Error: trx step error`, + ]) + }) + }) + }) + describe('transaction commit error occurs', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest + .fn() + .mockReturnValueOnce({ count: 1 }) + .mockReturnValueOnce({ count: 1 }) + .mockReturnValueOnce({ all: jest.fn().mockReturnValue([]) }) + .mockReturnValueOnce({ all: jest.fn().mockReturnValue([]) }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn(), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), + }), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: 123, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = [new GraphQLError('Unable to update domain. Please try again.')] + + expect(response.errors).toEqual(error) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('domain cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 1)}" + orgId: "${toGlobalId('organization', 1)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn(), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 400, + description: 'Impossible de mettre à jour un domaine inconnu.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 1, however there is no domain associated with that id.`, + ]) + }) + }) + describe('organization cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 1)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 400, + description: 'Impossible de mettre à jour le domaine dans un org inconnu.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 123 for org: 1, however there is no org associated with that id.`, + ]) + }) + }) + describe('user does not belong to org', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue(undefined), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 403, + description: + "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 123 for org: 123, however they do not have permission in that org.`, + ]) + }) + }) + describe('domain and org do not have any edges', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ count: 0 }), + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = { + data: { + updateDomain: { + result: { + code: 400, + description: "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update domain: 123 for org: 123, however that org has no claims to that domain.`, + ]) + }) + }) + describe('database error occurs', () => { + describe('while checking for edge connections', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockRejectedValue(new Error('database error')), + collections: collectionNames, + transaction, + publish, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Database error occurred while user: 123 attempted to update domain: 123, error: Error: database error`, + ]) + }) + }) + }) + describe('transaction step error occurs', () => { + describe('when running domain upsert', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest.fn().mockReturnValue({ count: 1 }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('trx step error')), + abort: jest.fn(), + }), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: 123, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Transaction step error occurred when user: 123 attempted to update domain: 123, error: Error: trx step error`, + ]) + }) + }) + }) + describe('transaction commit error occurs', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomain ( + input: { + domainId: "${toGlobalId('domain', 123)}" + orgId: "${toGlobalId('organization', 123)}" + assetState: APPROVED + } + ) { + result { + ... on Domain { + id + domain + lastRan + selectors + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: jest + .fn() + .mockReturnValue({ count: 1 }) + .mockReturnValueOnce({ count: 1 }) + .mockReturnValueOnce({ all: jest.fn().mockReturnValue([]) }) + .mockReturnValueOnce({ all: jest.fn().mockReturnValue([]) }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn(), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), + }), + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + tfaRequired: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), + loadDomainByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { load: jest.fn() }, + }, + }, + }) + + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + }) + }) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/update-domains-by-domain-ids.test.js b/api/src/domain/mutations/__tests__/update-domains-by-domain-ids.test.js new file mode 100644 index 0000000000..224233b50b --- /dev/null +++ b/api/src/domain/mutations/__tests__/update-domains-by-domain-ids.test.js @@ -0,0 +1,236 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, saltedHash, verifiedRequired, tfaRequired } from '../../../auth' +import { loadTagByTagId } from '../../../tags/loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env + +describe('updateDomainsByDomainIds mutation', () => { + let query, drop, i18n, truncate, schema, collections, transaction, user, org, tag + + const consoleOutput = [] + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.warn = mockedWarn + console.error = mockedError + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + tag = await collections.tags.save({ + tagId: 'tag-1', + tagName: 'Test Tag', + visible: true, + ownership: 'global', + organizations: [org._key], + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('user has super admin permission', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it('updates domains by domainIds', async () => { + // Insert a domain and claim for the org + const domain = await collections.domains.save({ domain: 'test.domain.gov' }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + tags: [], + }) + const response = await graphql({ + schema, + source: ` + mutation { + updateDomainsByDomainIds( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + tags: ["${tag.tagId}"] + domainIds: ["${toGlobalId('domains', domain._key)}"] + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { language: 'en' }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ userKey: user._key, loadUserByKey: loadUserByKey({ query }) }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadTagByTagId: loadTagByTagId({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + expect(response.data.updateDomainsByDomainIds.result.status).toMatch( + /Successfully updated 1 domain\(s\) in treasury-board-secretariat/, + ) + }) + }) + + describe('user does not have permission', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomainsByDomainIds( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + tags: ["${tag.tagId}"] + domainIds: [] + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { language: 'en' }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ userKey: user._key, loadUserByKey: loadUserByKey({ query }) }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadTagByTagId: loadTagByTagId({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + expect(response.data.updateDomainsByDomainIds.result.code).toBe(400) + expect(response.data.updateDomainsByDomainIds.result.description).toMatch(/Permission Denied/) + }) + }) +}) diff --git a/api/src/domain/mutations/__tests__/update-domains-by-filters.test.js b/api/src/domain/mutations/__tests__/update-domains-by-filters.test.js new file mode 100644 index 0000000000..373aa89997 --- /dev/null +++ b/api/src/domain/mutations/__tests__/update-domains-by-filters.test.js @@ -0,0 +1,238 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, saltedHash, verifiedRequired, tfaRequired } from '../../../auth' +import { loadTagByTagId } from '../../../tags/loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env + +describe('updateDomainsByFilters mutation', () => { + let query, drop, i18n, truncate, schema, collections, transaction, user, org, tag + + const consoleOutput = [] + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.warn = mockedWarn + console.error = mockedError + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + tag = await collections.tags.save({ + tagId: 'tag-1', + tagName: 'Test Tag', + visible: true, + ownership: 'global', + organizations: [org._key], + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('user has super admin permission', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + }) + it('updates domains by filters', async () => { + // Insert a domain and claim for the org + const domain = await collections.domains.save({ domain: 'test.domain.gov' }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + tags: [], + }) + const response = await graphql({ + schema, + source: ` + mutation { + updateDomainsByFilters( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + tags: ["${tag.tagId}"] + filters: [] + search: "test.domain.gov" + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { language: 'en' }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ userKey: user._key, loadUserByKey: loadUserByKey({ query }) }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadTagByTagId: loadTagByTagId({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + expect(response.data.updateDomainsByFilters.result.status).toMatch( + /Successfully updated 1 domain\(s\) in treasury-board-secretariat/, + ) + }) + }) + + describe('user does not have permission', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateDomainsByFilters( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + tags: ["${tag.tagId}"] + filters: [] + search: "test.domain.gov" + } + ) { + result { + ... on DomainBulkResult { + status + } + ... on DomainError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { language: 'en' }, + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ userKey: user._key, query }), + saltedHash: saltedHash(HASHING_SECRET), + userRequired: userRequired({ userKey: user._key, loadUserByKey: loadUserByKey({ query }) }), + verifiedRequired: verifiedRequired({}), + tfaRequired: tfaRequired({}), + }, + loaders: { + loadTagByTagId: loadTagByTagId({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + validators: { cleanseInput }, + }, + }) + expect(response.data.updateDomainsByFilters.result.code).toBe(400) + expect(response.data.updateDomainsByFilters.result.description).toMatch(/Permission Denied/) + }) + }) +}) diff --git a/api/src/domain/mutations/add-organizations-domains.js b/api/src/domain/mutations/add-organizations-domains.js new file mode 100644 index 0000000000..57d243ffc5 --- /dev/null +++ b/api/src/domain/mutations/add-organizations-domains.js @@ -0,0 +1,337 @@ +import { GraphQLNonNull, GraphQLList, GraphQLID, GraphQLBoolean } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { bulkModifyDomainsUnion } from '../unions' +import { Domain } from '../../scalars' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { AssetStateEnums } from '../../enums' + +export const addOrganizationsDomains = new mutationWithClientMutationId({ + name: 'AddOrganizationsDomains', + description: 'Mutation used to create multiple new domains for an organization.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish to assign this domain to.', + }, + domains: { + type: new GraphQLNonNull(new GraphQLList(Domain)), + description: 'Url that you would like to be added to the database.', + }, + tagNewDomains: { + type: GraphQLBoolean, + description: 'New domains will be tagged with NEW.', + }, + tagStagingDomains: { + type: GraphQLBoolean, + description: 'New domains will be tagged with STAGING.', + }, + assetState: { + type: AssetStateEnums, + description: "State each new domain will be given to define its relation to the org's attack surface", + }, + audit: { + type: GraphQLBoolean, + description: 'Audit logs will be created.', + }, + }), + outputFields: () => ({ + result: { + type: bulkModifyDomainsUnion, + description: '`BulkModifyDomainsUnion` returning either a `DomainBulkResult`, or `DomainErrorType` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + request, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, saltedHash, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadDomainByDomain, loadOrgByKey }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Cleanse input + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + let domains + if (typeof args.domains !== 'undefined') { + domains = args.domains.map((domain) => cleanseInput(domain)) + } else { + domains = [] + } + + let tagNewDomains + if (typeof args.tagNewDomains !== 'undefined') { + tagNewDomains = args.tagNewDomains + } else { + tagNewDomains = false + } + + let tagStagingDomains + if (typeof args.tagStagingDomains !== 'undefined') { + tagStagingDomains = args.tagStagingDomains + } else { + tagStagingDomains = false + } + + const assetState = cleanseInput(args.assetState) || 'approved' + + let audit + if (typeof args.audit !== 'undefined') { + audit = args.audit + } else { + audit = false + } + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn(`User: ${userKey} attempted to add domains to an organization: ${orgId} that does not exist.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to add domains in unknown organization.`), + } + } + + // Check to see if user belongs to org + const permission = await checkPermission({ orgId: org._id }) + + if (permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to add domains in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization user for help with creating domains.`), + } + } + + const tags = [] + if (tagNewDomains) { + tags.push('new-nouveau') + } + if (tagStagingDomains) { + tags.push('staging-dév') + } + + const updatedProperties = [] + if (typeof tags !== 'undefined' && tags.length > 0) { + updatedProperties.push({ + name: 'tags', + oldValue: [], + newValue: tags, + }) + } + + let domainCount = 0 + + for (const domain of domains) { + const insertDomain = { + domain: domain.toLowerCase(), + lastRan: null, + selectors: [], + hash: saltedHash(domain.toLowerCase()), + status: { + dkim: null, + dmarc: null, + https: null, + spf: null, + ssl: null, + }, + archived: false, + } + + // Check to see if domain already belongs to same org + let checkDomainCursor + try { + checkDomainCursor = await query` + WITH claims, domains, organizations + LET domainIds = (FOR domain IN domains FILTER domain.domain == ${insertDomain.domain} RETURN { id: domain._id }) + FOR domainId IN domainIds + LET domainEdges = (FOR v, e IN 1..1 ANY domainId.id claims RETURN { _from: e._from }) + FOR domainEdge IN domainEdges + LET org = DOCUMENT(domainEdge._from) + FILTER org._key == ${org._key} + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev }, TRANSLATE(${request.language}, org.orgDetails)) + ` + } catch (err) { + console.error(`Database error occurred while running check to see if domain already exists in an org: ${err}`) + continue + } + + let checkOrgDomain + try { + checkOrgDomain = await checkDomainCursor.next() + } catch (err) { + console.error(`Cursor error occurred while running check to see if domain already exists in an org: ${err}`) + continue + } + + if (typeof checkOrgDomain !== 'undefined') { + console.warn( + `User: ${userKey} attempted to create a domain for: ${org.slug}, however that org already has that domain claimed.`, + ) + continue + } + + // Check to see if domain already exists in db + const checkDomain = await loadDomainByDomain.load(insertDomain.domain) + + // Setup Transaction + const trx = await transaction(collections) + + let insertedDomainCursor + if (typeof checkDomain === 'undefined') { + try { + insertedDomainCursor = await trx.step( + () => + query` + WITH domains + INSERT ${insertDomain} INTO domains + RETURN MERGE( + { + id: NEW._key, + _type: "domain" + }, + NEW + ) + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when inserting new domain: ${err}`) + await trx.abort() + continue + } + + let insertedDomain + try { + insertedDomain = await insertedDomainCursor.next() + } catch (err) { + console.error( + `Cursor error occurred for user: ${userKey} after inserting new domain and gathering its domain info: ${err}`, + ) + await trx.abort() + continue + } + + try { + await trx.step( + () => + query` + WITH claims + INSERT { + _from: ${org._id}, + _to: ${insertedDomain._id}, + tags: ${tags}, + assetState: ${assetState}, + } INTO claims + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when inserting new domain edge: ${err}`) + await trx.abort() + continue + } + } else { + try { + await trx.step( + () => + query` + WITH claims + INSERT { + _from: ${org._id}, + _to: ${checkDomain._id}, + tags: ${tags}, + } INTO claims + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when inserting domain edge: ${err}`) + await trx.abort() + continue + } + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userKey} was creating domains: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to create domains. Please try again.`)) + } + + if (audit) { + console.info(`User: ${userKey} successfully added domain: ${insertDomain.domain} to org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'add', + target: { + resource: insertDomain.domain, + updatedProperties, + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + } + domainCount += 1 + } + + if (!audit) { + console.info(`User: ${userKey} successfully added ${domainCount} domain(s) to org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'add', + target: { + resource: `${domainCount} domains`, + updatedProperties, + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + } + + return { + _type: 'result', + status: i18n._(t`Successfully added ${domainCount} domain(s) to ${org.slug}.`), + } + }, +}) diff --git a/api/src/domain/mutations/create-domain.js b/api/src/domain/mutations/create-domain.js new file mode 100644 index 0000000000..4460f8f55e --- /dev/null +++ b/api/src/domain/mutations/create-domain.js @@ -0,0 +1,355 @@ +import { GraphQLNonNull, GraphQLList, GraphQLID, GraphQLBoolean, GraphQLString } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { createDomainUnion } from '../unions' +import { Domain } from '../../scalars' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { AssetStateEnums } from '../../enums' +import { headers } from 'nats' +import { CvdEnrollmentInputOptions } from '../../additional-findings/input/cvd-enrollment-options' +import ac from '../../access-control' + +export const createDomain = new mutationWithClientMutationId({ + name: 'CreateDomain', + description: 'Mutation used to create a new domain for an organization.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish to assign this domain to.', + }, + domain: { + type: new GraphQLNonNull(Domain), + description: 'Url that you would like to be added to the database.', + }, + tags: { + description: 'List of labelled tags users have applied to the domain.', + type: new GraphQLList(GraphQLString), + }, + archived: { + description: 'Value that determines if the domain is excluded from the scanning process.', + type: GraphQLBoolean, + }, + assetState: { + description: 'Value that determines how the domain relates to the organization.', + type: new GraphQLNonNull(AssetStateEnums), + }, + cvdEnrollment: { + description: + 'The Coordinated Vulnerability Disclosure (CVD) enrollment details for this domain, including HackerOne integration status and CVSS requirements.', + type: CvdEnrollmentInputOptions, + }, + }), + outputFields: () => ({ + result: { + type: createDomainUnion, + description: '`CreateDomainUnion` returning either a `Domain`, or `CreateDomainError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + request, + query, + collections, + transaction, + userKey, + publish, + auth: { checkPermission, saltedHash, userRequired, tfaRequired, verifiedRequired }, + loaders: { loadDomainByDomain, loadOrgByKey, loadTagByTagId }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Cleanse input + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + const domain = cleanseInput(args.domain) + + let tags + if (typeof args.tags !== 'undefined') { + tags = await loadTagByTagId.loadMany( + args.tags.map((tag) => { + return cleanseInput(tag) + }), + ) + tags = tags + .filter(({ visible, ownership, organizations }) => { + // Filter out tags that are not visible or do not belong to the org + return visible && (ownership === 'global' || organizations.some((org) => org._id === orgId)) + }) + .map((tag) => tag.tagId) + } else { + tags = [] + } + + let archived + if (typeof args.archived !== 'undefined') { + archived = args.archived + } else { + archived = false + } + + let assetState + if (typeof args.assetState !== 'undefined') { + assetState = cleanseInput(args.assetState) + } else { + assetState = 'approved' + } + + const cvdEnrollment = args.cvdEnrollment || { status: 'not-enrolled' } + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn(`User: ${userKey} attempted to create a domain to an organization: ${orgId} that does not exist.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to create domain in unknown organization.`), + } + } + + // Check to see if user belongs to org + const permission = await checkPermission({ orgId: org._id }) + + if (!ac.can(permission).createOwn('domain').granted) { + console.warn( + `User: ${userKey} attempted to create a domain in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization user for help with creating domain.`), + } + } + + // ensure only owners can enroll or deny domains + if ( + !ac.can(permission).createOwn('cvd-enrollment').granted && + ['enrolled', 'deny'].includes(cvdEnrollment.status) + ) { + console.warn( + `User: ${userKey} attempted to update the CVD enrollment for domain: ${domain} in org: ${orgId}, however they do not have permission in that org.`, + ) + cvdEnrollment.status = cvdEnrollment.status === 'enrolled' ? 'pending' : 'not-enrolled' + } + + const insertDomain = { + domain: domain.toLowerCase(), + lastRan: null, + hash: saltedHash(domain.toLowerCase()), + status: { + certificates: 'info', + ciphers: 'info', + curves: 'info', + dkim: 'info', + dmarc: 'info', + hsts: 'info', + https: 'info', + protocols: 'info', + spf: 'info', + ssl: 'info', + }, + archived, + ignoreRua: false, + cvdEnrollment, + } + + // Check to see if domain already belongs to same org + let checkDomainCursor + try { + checkDomainCursor = await query` + WITH claims, domains, organizations + LET domainIds = (FOR domain IN domains FILTER domain.domain == ${insertDomain.domain} RETURN { id: domain._id }) + FOR domainId IN domainIds + LET domainEdges = (FOR v, e IN 1..1 ANY domainId.id claims RETURN { _from: e._from }) + FOR domainEdge IN domainEdges + LET org = DOCUMENT(domainEdge._from) + FILTER org._key == ${org._key} + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev }, TRANSLATE(${request.language}, org.orgDetails)) + ` + } catch (err) { + console.error(`Database error occurred while running check to see if domain already exists in an org: ${err}`) + throw new Error(i18n._(t`Unable to create domain. Please try again.`)) + } + + let checkOrgDomain + try { + checkOrgDomain = await checkDomainCursor.next() + } catch (err) { + console.error(`Cursor error occurred while running check to see if domain already exists in an org: ${err}`) + throw new Error(i18n._(t`Unable to create domain. Please try again.`)) + } + + if (typeof checkOrgDomain !== 'undefined') { + console.warn( + `User: ${userKey} attempted to create a domain for: ${org.slug}, however that org already has that domain claimed.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to create domain, organization has already claimed it.`), + } + } + + // Setup Transaction + const trx = await transaction(collections) + + let domainCursor + try { + domainCursor = await trx.step( + () => + query` + UPSERT { domain: ${insertDomain.domain} } + INSERT ${insertDomain} + UPDATE { } + IN domains + RETURN NEW + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when inserting new domain: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to create domain. Please try again.`)) + } + + let insertedDomain + try { + insertedDomain = await domainCursor.next() + } catch (err) { + console.error(`Cursor error occurred for user: ${userKey} when inserting new domain: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to create domain. Please try again.`)) + } + + try { + await trx.step( + () => + query` + WITH claims + INSERT { + _from: ${org._id}, + _to: ${insertedDomain._id}, + tags: ${tags}, + assetState: ${assetState}, + firstSeen: ${new Date().toISOString()}, + } INTO claims + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when inserting new domain edge: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to create domain. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userKey} was creating domain: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to create domain. Please try again.`)) + } + + // Clear dataloader incase anything was updated or inserted into domain + await loadDomainByDomain.clear(insertDomain.domain) + const returnDomain = await loadDomainByDomain.load(insertDomain.domain) + + console.info(`User: ${userKey} successfully created ${returnDomain.domain} in org: ${org.slug}.`) + + const updatedProperties = [] + if (typeof tags !== 'undefined' && tags.length > 0) { + updatedProperties.push({ + name: 'tags', + oldValue: [], + newValue: tags, + }) + } + + if (typeof assetState !== 'undefined') { + updatedProperties.push({ + name: 'assetState', + oldValue: null, + newValue: assetState, + }) + } + + if (typeof cvdEnrollment !== 'undefined') { + updatedProperties.push({ + name: 'cvdEnrollment', + oldValue: null, + newValue: cvdEnrollment.enrollment, + }) + } + + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: request.ip, + }, + action: 'add', + target: { + resource: insertDomain.domain, + updatedProperties, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }) + + const hdrs = headers() + hdrs.set('priority', 'high') + + try { + await publish({ + channel: 'scans.requests_priority', + msg: { + domain: returnDomain.domain, + domain_key: returnDomain._key, + hash: returnDomain.hash, + user_key: null, // only used for One Time Scans + shared_id: null, // only used for One Time Scans + }, + options: { + headers: hdrs, + }, + }) + } catch (err) { + console.error(`Error publishing to NATS for domain ${returnDomain._key}: ${err}`) + } + + try { + await publish({ + channel: 'scans.add_domain_to_easm', + msg: { + domain: returnDomain.domain, + domain_key: returnDomain._key, + hash: returnDomain.hash, + user_key: null, // only used for One Time Scans + shared_id: null, // only used for One Time Scans + }, + }) + } catch (err) { + console.error(`Error publishing to NATS for domain ${returnDomain._key}: ${err}`) + } + + return { + ...returnDomain, + claimTags: tags, + } + }, +}) diff --git a/api/src/domain/mutations/favourite-domain.js b/api/src/domain/mutations/favourite-domain.js new file mode 100644 index 0000000000..e70014c179 --- /dev/null +++ b/api/src/domain/mutations/favourite-domain.js @@ -0,0 +1,120 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { createDomainUnion } from '../unions' + +export const favouriteDomain = new mutationWithClientMutationId({ + name: 'FavouriteDomain', + description: "Mutation to add domain to user's personal myTracker view.", + inputFields: () => ({ + domainId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the domain you wish to favourite.', + }, + }), + outputFields: () => ({ + result: { + type: createDomainUnion, + description: '`CreateDomainUnion` returning either a `Domain`, or `CreateDomainError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + auth: { userRequired, verifiedRequired }, + loaders: { loadDomainByKey }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Cleanse input + const { type: _domainType, id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + + // Get domain from db + const domain = await loadDomainByKey.load(domainId) + // Check to see if domain exists + if (typeof domain === 'undefined') { + console.warn(`User: ${userKey} attempted to favourite ${domainId} however no domain is associated with that id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to favourite unknown domain.`), + } + } + + // Check to see if domain already favourited by user + let checkDomainCursor + try { + checkDomainCursor = await query` + WITH domains + FOR v, e IN 1..1 ANY ${domain._id} favourites + FILTER e._from == ${user._id} + RETURN e + ` + } catch (err) { + console.error(`Database error occurred while running check to see if domain already favourited: ${err}`) + throw new Error(i18n._(t`Unable to favourite domain. Please try again.`)) + } + + let checkUserDomain + try { + checkUserDomain = await checkDomainCursor.next() + } catch (err) { + console.error(`Cursor error occurred while running check to see if domain already favourited: ${err}`) + throw new Error(i18n._(t`Unable to favourite domain. Please try again.`)) + } + + if (typeof checkUserDomain !== 'undefined') { + console.warn(`User: ${userKey} attempted to favourite a domain, however user already has that domain favourited.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to favourite domain, user has already favourited it.`), + } + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => + query` + WITH favourites + INSERT { + _from: ${user._id}, + _to: ${domain._id}, + } INTO favourites + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when inserting new domain edge: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to favourite domain. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userKey} was creating domain: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to favourite domain. Please try again.`)) + } + + console.info(`User: ${userKey} successfully favourited domain ${domain.domain}.`) + + return { + ...domain, + } + }, +}) diff --git a/api/src/domain/mutations/ignore-cve.js b/api/src/domain/mutations/ignore-cve.js new file mode 100644 index 0000000000..535854be0f --- /dev/null +++ b/api/src/domain/mutations/ignore-cve.js @@ -0,0 +1,233 @@ +import { GraphQLID, GraphQLNonNull } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { CveID } from '../../scalars' +import { ignoreCveUnion } from '../unions/ignore-cve-union' +import { logActivity } from '../../audit-logs' + +export const ignoreCve = new mutationWithClientMutationId({ + name: 'IgnoreCve', + description: 'Ignore a CVE for a domain.', + inputFields: () => ({ + domainId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the domain which is ignoring the CVE.', + }, + ignoredCve: { + description: 'The CVE ID that is being ignored.', + type: CveID, + }, + }), + outputFields: () => ({ + result: { + type: ignoreCveUnion, + description: '`IgnoreCveUnion` returning either a `Domain` or an error', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { userRequired, checkSuperAdmin, superAdminRequired, verifiedRequired, tfaRequired }, + validators: { cleanseInput }, + loaders: { loadDomainByKey }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + tfaRequired({ user }) + + // Only super admins can ignore CVEs + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + const { id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + + const ignoredCve = cleanseInput(args.ignoredCve) + + // Check to see if domain exists + const domain = await loadDomainByKey.load(domainId) + + if (typeof domain === 'undefined') { + console.warn(`User: "${userKey}" attempted to ignore CVE "${ignoredCve}" on unknown domain: "${domainId}".`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to ignore CVE. Please try again.`), + } + } + + const oldIgnoredCves = domain.ignoredCves || [] + + if (oldIgnoredCves.includes(ignoredCve)) { + console.warn( + `User: "${userKey}" attempted to ignore CVE "${ignoredCve}" on domain: "${domainId}" however CVE is already ignored.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`CVE is already ignored for this domain.`), + } + } + + const newIgnoredCves = Array.from(new Set([...oldIgnoredCves, ignoredCve])) + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + async () => + await query` + UPSERT { _key: ${domain._key} } + INSERT ${{ ignoredCves: newIgnoredCves }} + UPDATE ${{ ignoredCves: newIgnoredCves }} + IN domains + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: "${userKey}" attempted to ignore CVE "${ignoredCve}" on domain "${domainId}", error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to ignore CVE. Please try again.`)) + } + + let currentDomainVulnerabilitiesCursor + try { + currentDomainVulnerabilitiesCursor = await trx.step( + () => query` + FOR finding IN additionalFindings + FILTER finding.domain == ${domain._id} + LIMIT 1 + FOR wc IN finding.webComponents + FILTER LENGTH(wc.WebComponentCves) > 0 + FOR vuln IN wc.WebComponentCves + FILTER vuln.Cve NOT IN ${newIgnoredCves} + RETURN DISTINCT vuln.Cve + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: "${userKey}" attempted to ignore CVE "${ignoredCve}" on domain "${domainId}" when getting current CVEs, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to ignore CVE. Please try again.`)) + } + + try { + await trx.step( + () => + query` + UPDATE { _key: ${domain._key}, cveDetected: ${currentDomainVulnerabilitiesCursor.count > 0} } IN domains + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: "${userKey}" attempted to ignore CVE "${ignoredCve}" on domain "${domainId}" when updating domain, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to ignore CVE. Please try again.`)) + } + + // Commit transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred when user: "${userKey}" attempted to ignore CVE "${ignoredCve}" on domain "${domainId}", error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to ignore CVE. Please try again.`)) + } + + // Get all verified claims to domain and activityLog those organizations + try { + const orgs = await query` + FOR v, e IN 1..1 INBOUND ${domain._id} claims + FILTER v.verified == true + RETURN { + _key: v._key, + name: v.orgDetails.en.orgName, + } + ` + for await (const org of orgs) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: 'super_admin', + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain.domain, + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + updatedProperties: [ + { + name: ignoredCve, + oldValue: 'unignored', + newValue: 'ignored', + }, + ], + }, + }) + } + // Log activity for super admin logging + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: 'super_admin', + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain.domain, + resourceType: 'domain', + updatedProperties: [ + { + name: ignoredCve, + oldValue: 'unignored', + newValue: 'ignored', + }, + ], + }, + }) + } catch (err) { + console.error( + `Database error occurred when user: "${userKey}" attempted to ignore CVE "${ignoredCve}" on domain "${domainId}" during activity logs, error: ${err}`, + ) + } + + // Clear dataloader and load updated domain + await loadDomainByKey.clear(domain._key) + const returnDomain = await loadDomainByKey.load(domain._key) + + console.info(`User: "${userKey}" successfully ignored CVE "${ignoredCve}" on domain: "${domainId}".`) + + returnDomain.id = returnDomain._key + + return { + ...returnDomain, + } + }, +}) diff --git a/api/src/domain/mutations/index.js b/api/src/domain/mutations/index.js new file mode 100644 index 0000000000..295cc20cea --- /dev/null +++ b/api/src/domain/mutations/index.js @@ -0,0 +1,13 @@ +export * from './add-organizations-domains' +export * from './create-domain' +export * from './favourite-domain' +export * from './ignore-cve' +export * from './remove-domain' +export * from './remove-organizations-domains' +export * from './request-discovery' +export * from './request-scan' +export * from './unfavourite-domain' +export * from './unignore-cve' +export * from './update-domain' +export * from './update-domains-by-domain-ids' +export * from './update-domains-by-filters' diff --git a/api/src/domain/mutations/remove-domain.js b/api/src/domain/mutations/remove-domain.js new file mode 100644 index 0000000000..bde0ff54af --- /dev/null +++ b/api/src/domain/mutations/remove-domain.js @@ -0,0 +1,377 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { removeDomainUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { DomainRemovalReasonEnum } from '../../enums' +import ac from '../../access-control' + +export const removeDomain = new mutationWithClientMutationId({ + name: 'RemoveDomain', + description: 'This mutation allows the removal of unused domains.', + inputFields: () => ({ + domainId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the domain you wish to remove.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The organization you wish to remove the domain from.', + }, + reason: { + type: new GraphQLNonNull(DomainRemovalReasonEnum), + description: 'The reason given for why this domain is being removed from the organization.', + }, + }), + outputFields: () => ({ + result: { + type: new GraphQLNonNull(removeDomainUnion), + description: '`RemoveDomainUnion` returning either a `DomainResultType`, or `DomainErrorType` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + validators: { cleanseInput }, + loaders: { loadDomainByKey, loadOrgByKey }, + }, + ) => { + // Get User + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Cleanse Input + const { type: _domainType, id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + // Get domain from db + const domain = await loadDomainByKey.load(domainId) + + // Check to see if domain exists + if (typeof domain === 'undefined') { + console.warn(`User: ${userKey} attempted to remove ${domainId} however no domain is associated with that id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove unknown domain.`), + } + } + + // Get Org from db + const org = await loadOrgByKey.load(orgId) + + // Check to see if org exists + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to remove ${domain.domain} in org: ${orgId} however there is no organization associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove domain from unknown organization.`), + } + } + + // Get permission + const permission = await checkPermission({ orgId: org._id }) + + if (!ac.can(permission).deleteOwn('domain').granted) { + console.warn( + `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} however they do not have permission in that org.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing domain.`), + } + } + + // Check to see if domain belongs to verified check org + // if domain returns NXDOMAIN, allow removal + if (org.verified && !ac.can(permission).deleteAny('domain').granted && domain.rcode !== 'NXDOMAIN') { + console.warn( + `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} but does not have permission to remove a domain from a verified check org.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with removing domain.`), + } + } + + // Check to see if more than one organization has a claim to this domain + let countCursor + try { + countCursor = await query` + WITH claims, domains, organizations + FOR v, e IN 1..1 ANY ${domain._id} claims + RETURN v + ` + } catch (err) { + console.error( + `Database error occurred for user: ${userKey}, when counting domain claims for domain: ${domain.domain}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + // check if org has claim to domain + const orgsClaimingDomain = await countCursor.all() + const orgHasDomainClaim = orgsClaimingDomain.some((orgVertex) => { + return orgVertex._id === org._id + }) + + if (!orgHasDomainClaim) { + console.error( + `Error occurred for user: ${userKey}, when attempting to remove domain "${domain.domain}" from organization with slug "${org.slug}". Organization does not have claim for domain.`, + ) + throw new Error(i18n._(t`Unable to remove domain. Domain is not part of organization.`)) + } + + // check to see if org removing domain has ownership + let dmarcCountCursor + try { + dmarcCountCursor = await query` + WITH domains, organizations, ownership + FOR v IN 1..1 OUTBOUND ${org._id} ownership + FILTER v._id == ${domain._id} + RETURN true + ` + } catch (err) { + console.error( + `Database error occurred for user: ${userKey}, when counting ownership claims for domain: ${domain.domain}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + // Setup Transaction + const trx = await transaction(collections) + + if (dmarcCountCursor.count === 1) { + try { + await trx.step( + () => query` + WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries + LET dmarcSummaryEdges = ( + FOR v, e IN 1..1 OUTBOUND ${domain._id} domainsToDmarcSummaries + RETURN { edgeKey: e._key, dmarcSummaryId: e._to } + ) + LET removeDmarcSummaryEdges = ( + FOR dmarcSummaryEdge IN dmarcSummaryEdges + REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries + OPTIONS { waitForSync: true } + ) + LET removeDmarcSummary = ( + FOR dmarcSummaryEdge IN dmarcSummaryEdges + LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key + REMOVE key IN dmarcSummaries + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing dmarc summary data for user: ${userKey} while attempting to remove domain: ${domain.domain}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + try { + await trx.step( + () => query` + WITH ownership, organizations, domains + LET domainEdges = ( + FOR v, e IN 1..1 INBOUND ${domain._id} ownership + REMOVE e._key IN ownership + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing ownership data for user: ${userKey} while attempting to remove domain: ${domain.domain}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + } + + if (countCursor.count <= 1) { + // Remove scan data + + try { + // Remove web data + await trx.step(async () => { + await query` + WITH web, webScan, domains + FOR webV, domainsWebEdge IN 1..1 OUTBOUND ${domain._id} domainsWeb + LET removeWebScansQuery = ( + FOR webScanV, webToWebScansV In 1..1 OUTBOUND webV._id webToWebScans + REMOVE webScanV IN webScan + REMOVE webToWebScansV IN webToWebScans + OPTIONS { waitForSync: true } + ) + REMOVE webV IN web + REMOVE domainsWebEdge IN domainsWeb + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove web data for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + try { + // Remove DNS data + await trx.step(async () => { + await query` + WITH dns, domains + FOR dnsV, domainsDNSEdge IN 1..1 OUTBOUND ${domain._id} domainsDNS + REMOVE dnsV IN dns + REMOVE domainsDNSEdge IN domainsDNS + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove DNS data for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + // remove favourites + try { + await trx.step(async () => { + await query` + WITH favourites, domains + FOR fav IN favourites + FILTER fav._to == ${domain._id} + REMOVE fav IN favourites + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove favourites for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + // remove DKIM selectors + try { + await trx.step(async () => { + await query` + FOR e IN domainsToSelectors + FILTER e._from == ${domain._id} + REMOVE e IN domainsToSelectors + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove DKIM selectors for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + try { + // Remove domain + await trx.step(async () => { + await query` + FOR claim IN claims + FILTER claim._to == ${domain._id} + REMOVE claim IN claims + REMOVE ${domain} IN domains + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove domain ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + } else { + try { + await trx.step(async () => { + await query` + WITH claims, domains, organizations + LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} claims RETURN { _key: e._key, _from: e._from, _to: e._to }) + LET edgeKeys = ( + FOR domainEdge IN domainEdges + FILTER domainEdge._to == ${domain._id} + FILTER domainEdge._from == ${org._id} + RETURN domainEdge._key + ) + FOR edgeKey IN edgeKeys + REMOVE edgeKey IN claims + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove claim for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + } + + // Commit transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Trx commit error occurred while user: ${userKey} attempted to remove ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to remove domain. Please try again.`)) + } + + console.info(`User: ${userKey} successfully removed domain: ${domain.domain} from org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'remove', + target: { + resource: domain.domain, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + reason: args.reason, + }) + + return { + _type: 'result', + status: i18n._(t`Successfully removed domain: ${domain.domain} from ${org.slug}.`), + domain, + } + }, +}) diff --git a/api/src/domain/mutations/remove-organizations-domains.js b/api/src/domain/mutations/remove-organizations-domains.js new file mode 100644 index 0000000000..3934f8a4ec --- /dev/null +++ b/api/src/domain/mutations/remove-organizations-domains.js @@ -0,0 +1,480 @@ +import { GraphQLNonNull, GraphQLID, GraphQLBoolean, GraphQLList } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { bulkModifyDomainsUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { Domain } from '../../scalars' + +export const removeOrganizationsDomains = new mutationWithClientMutationId({ + name: 'RemoveOrganizationsDomains', + description: 'This mutation allows the removal of unused domains.', + inputFields: () => ({ + domains: { + type: new GraphQLNonNull(new GraphQLList(Domain)), + description: 'Domains you wish to remove from the organization.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The organization you wish to remove the domain from.', + }, + archiveDomains: { + type: GraphQLBoolean, + description: 'Domains will be archived.', + }, + audit: { + type: GraphQLBoolean, + description: 'Audit logs will be created.', + }, + }), + outputFields: () => ({ + result: { + type: new GraphQLNonNull(bulkModifyDomainsUnion), + description: '`BulkModifyDomainsUnion` returning either a `DomainBulkResult`, or `DomainErrorType` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + validators: { cleanseInput }, + loaders: { loadDomainByDomain, loadOrgByKey }, + }, + ) => { + // Get User + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + // Cleanse Input + let domains + if (typeof args.domains !== 'undefined') { + domains = args.domains.map((domain) => cleanseInput(domain)) + } else { + domains = [] + } + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + let audit + if (typeof args.audit !== 'undefined') { + audit = args.audit + } else { + audit = false + } + + let archiveDomains + if (typeof args.archiveDomains !== 'undefined') { + archiveDomains = args.archiveDomains + } else { + archiveDomains = false + } + + // Get Org from db + const org = await loadOrgByKey.load(orgId) + + // Check to see if org exists + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to remove domains in org: ${orgId} however there is no organization associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove domains from unknown organization.`), + } + } + + // Get permission + const permission = await checkPermission({ orgId: org._id }) + + // Check to see if domain belongs to verified check org + if (org.verified && permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to remove domains in ${org.slug} but does not have permission to remove a domain from a verified check org.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with removing domain.`), + } + } + + if (permission !== 'super_admin' && permission !== 'admin') { + console.warn( + `User: ${userKey} attempted to remove domains in ${org.slug} however they do not have permission in that org.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing domains.`), + } + } + + if (archiveDomains && permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to archive domains in ${org.slug} however they do not have permission in that org.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact organization admin for help with archiving domains.`), + } + } + + let domainCount = 0 + + for (const domain of domains) { + // Setup Transaction + const trx = await transaction(collections) + + // Get domain from db + const checkDomain = await loadDomainByDomain.load(domain) + + // Check to see if domain exists + if (typeof checkDomain === 'undefined') { + console.warn(`User: ${userKey} attempted to remove ${domain} however no domain is associated with that id.`) + continue + } + + if (archiveDomains && permission === 'super_admin') { + // Archive Domain + try { + await trx.step( + () => query` + UPDATE ${checkDomain} WITH { archived: true } IN domains + `, + ) + + if (audit) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: `${domains.length} domains`, + updatedProperties: [ + { + name: 'archived', + oldValue: checkDomain?.archived, + newValue: true, + }, + ], + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }) + } + } catch (err) { + console.error( + `Database error occurred for user: ${userKey} when attempting to archive domain: ${domain}, error: ${err}`, + ) + await trx.abort() + continue + } + } else { + // Check to see if more than one organization has a claim to this domain + let countCursor + try { + countCursor = await query` + WITH claims, domains, organizations + FOR v, e IN 1..1 ANY ${checkDomain._id} claims + RETURN v + ` + } catch (err) { + console.error( + `Database error occurred for user: ${userKey}, when counting domain claims for domain: ${checkDomain.domain}, error: ${err}`, + ) + await trx.abort() + continue + } + + // check if org has claim to domain + const orgsClaimingDomain = await countCursor.all() + const orgHasDomainClaim = orgsClaimingDomain.some((orgVertex) => { + return orgVertex._id === org._id + }) + + if (!orgHasDomainClaim) { + console.error( + `Error occurred for user: ${userKey}, when attempting to remove domain "${domain}" from organization with slug "${org.slug}". Organization does not have claim for domain.`, + ) + await trx.abort() + continue + } + + // check to see if org removing domain has ownership + let dmarcCountCursor + try { + dmarcCountCursor = await query` + WITH domains, organizations, ownership + FOR v IN 1..1 OUTBOUND ${org._id} ownership + FILTER v._id == ${checkDomain._id} + RETURN true + ` + } catch (err) { + console.error( + `Database error occurred for user: ${userKey}, when counting ownership claims for domain: ${checkDomain.domain}, error: ${err}`, + ) + await trx.abort() + continue + } + + if (dmarcCountCursor.count === 1) { + try { + await trx.step( + () => query` + WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries + LET dmarcSummaryEdges = ( + FOR v, e IN 1..1 OUTBOUND ${checkDomain._id} domainsToDmarcSummaries + RETURN { edgeKey: e._key, dmarcSummaryId: e._to } + ) + LET removeDmarcSummaryEdges = ( + FOR dmarcSummaryEdge IN dmarcSummaryEdges + REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries + OPTIONS { waitForSync: true } + ) + LET removeDmarcSummary = ( + FOR dmarcSummaryEdge IN dmarcSummaryEdges + LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key + REMOVE key IN dmarcSummaries + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing dmarc summary data for user: ${userKey} while attempting to remove domain: ${checkDomain.domain}, error: ${err}`, + ) + await trx.abort() + continue + } + + try { + await trx.step( + () => query` + WITH ownership, organizations, domains + LET domainEdges = ( + FOR v, e IN 1..1 INBOUND ${checkDomain._id} ownership + REMOVE e._key IN ownership + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing ownership data for user: ${userKey} while attempting to remove domain: ${checkDomain.domain}, error: ${err}`, + ) + await trx.abort() + continue + } + } + + if (countCursor.count <= 1) { + // Remove scan data + + try { + // Remove web data + await trx.step(async () => { + await query` + WITH web, webScan, domains + FOR webV, domainsWebEdge IN 1..1 OUTBOUND ${domain._id} domainsWeb + LET removeWebScansQuery = ( + FOR webScanV, webToWebScansV In 1..1 OUTBOUND webV._id webToWebScans + REMOVE webScanV IN webScan + REMOVE webToWebScansV IN webToWebScans + OPTIONS { waitForSync: true } + ) + REMOVE webV IN web + REMOVE domainsWebEdge IN domainsWeb + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove web data for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + continue + } + + try { + // Remove DNS data + await trx.step(async () => { + await query` + WITH dns, domains + FOR dnsV, domainsDNSEdge IN 1..1 OUTBOUND ${domain._id} domainsDNS + REMOVE dnsV IN dns + REMOVE domainsDNSEdge IN domainsDNS + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove DNS data for ${domain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + continue + } + + try { + // Remove domain + await trx.step(async () => { + await query` + FOR claim IN claims + FILTER claim._to == ${checkDomain._id} + REMOVE claim IN claims + REMOVE ${checkDomain} IN domains + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove domain ${checkDomain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + continue + } + } else { + try { + await trx.step(async () => { + await query` + WITH claims, domains, organizations + LET domainEdges = (FOR v, e IN 1..1 INBOUND ${checkDomain._id} claims RETURN { _key: e._key, _from: e._from, _to: e._to }) + LET edgeKeys = ( + FOR domainEdge IN domainEdges + FILTER domainEdge._to == ${checkDomain._id} + FILTER domainEdge._from == ${org._id} + RETURN domainEdge._key + ) + FOR edgeKey IN edgeKeys + REMOVE edgeKey IN claims + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error( + `Trx step error occurred while user: ${userKey} attempted to remove claim for ${checkDomain.domain} in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + continue + } + } + + // Commit transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Trx commit error occurred while user: ${userKey} attempted to remove domains in org: ${org.slug}, error: ${err}`, + ) + await trx.abort() + continue + } + + if (audit) { + console.info(`User: ${userKey} successfully removed domain: ${domain} from org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'remove', + target: { + resource: checkDomain.domain, + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + } + } + domainCount += 1 + } + + // Log activity + if (!audit) { + console.info(`User: ${userKey} successfully removed ${domainCount} domain(s) from org: ${org.slug}.`) + if (archiveDomains) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: `${domainCount} domains`, + updatedProperties: [ + { + name: 'archived', + oldValue: false, + newValue: true, + }, + ], + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + } else { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'remove', + target: { + resource: `${domainCount} domains`, + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + } + } + + return { + _type: 'result', + status: i18n._(t`Successfully removed ${domainCount} domain(s) from ${org.slug}.`), + } + }, +}) diff --git a/api/src/domain/mutations/request-discovery.js b/api/src/domain/mutations/request-discovery.js new file mode 100644 index 0000000000..237ca9c30c --- /dev/null +++ b/api/src/domain/mutations/request-discovery.js @@ -0,0 +1,137 @@ +import { t } from '@lingui/macro' +import { GraphQLID, GraphQLNonNull, GraphQLString } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' + +import { Domain } from '../../scalars' +import { logActivity } from '../../audit-logs' + +export const requestDiscovery = new mutationWithClientMutationId({ + name: 'RequestDiscovery', + description: 'This mutation is used to start a subdomain discovery scan on a requested domain.', + inputFields: () => ({ + domain: { + type: Domain, + description: 'The base domain that the subdomain scan will be ran on.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish to assign new found domains to.', + }, + }), + outputFields: () => ({ + status: { + type: GraphQLString, + description: 'Informs the user if the scan was dispatched successfully.', + resolve: ({ status }) => status, + }, + }), + mutateAndGetPayload: async ( + args, + { + query, + collections, + transaction, + i18n, + userKey, + publish, + request: { ip }, + auth: { checkDomainPermission, userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired }, + loaders: { loadDomainByDomain, loadOrgByKey }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Keep feature to super admins while in beta + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + // Cleanse input + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + const domainInput = cleanseInput(args.domain) + + // Check to see if domain is valid for subdomain discovery. + // Discovery should not be performed on a domain that is not a subdomain of canada.ca or gc.ca, or on the root domains themselves + const regex = /^[A-Za-z0-9](?:[A-Za-z0-9\-.]+[A-Za-z0-9])?.(canada|gc).ca$/gm + const found = domainInput.match(regex) + if (typeof found === 'undefined' || found?.length !== 1) { + console.warn( + `User: ${userKey} attempted to start a subdomain discovery scan on: ${domainInput} however domain is not a valid domain.`, + ) + throw new Error(i18n._(t`Unable to request a subdomain discovery scan on an invalid domain.`)) + } + + // Check to see if domain exists + const domain = await loadDomainByDomain.load(domainInput) + + if (typeof domain === 'undefined') { + console.warn( + `User: ${userKey} attempted to start a subdomain discovery scan on: ${domainInput} however domain cannot be found.`, + ) + throw new Error(i18n._(t`Unable to request a subdomain discovery scan on an unknown domain.`)) + } + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to discover subdomains in an organization: ${orgId} that does not exist.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to discover domains for unknown organization.`), + } + } + + // Check to see if user has access to domain + const permission = await checkDomainPermission({ domainId: domain._id }) + + if (!permission) { + console.warn( + `User: ${userKey} attempted to start a subdomain discovery scan on: ${domain.domain} however they do not have permission to do so.`, + ) + throw new Error( + i18n._(t`Permission Denied: Please contact organization user for help with scanning this domain.`), + ) + } + + await publish({ + channel: `scans.discovery`, + msg: { + domain: domain.domain, + orgId: org._id, + }, + }) + + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'discover', + target: { + resource: domain.domain, + organization: { + id: org._id, + name: org.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }) + + console.info(`User: ${userKey} successfully dispatched a subdomain discovery scan on domain: ${domain.domain}.`) + + return { + status: i18n._(t`Successfully dispatched subdomain discovery scan.`), + } + }, +}) diff --git a/api/src/domain/mutations/request-scan.js b/api/src/domain/mutations/request-scan.js new file mode 100644 index 0000000000..c75eff28ce --- /dev/null +++ b/api/src/domain/mutations/request-scan.js @@ -0,0 +1,178 @@ +import { t } from '@lingui/macro' +import { GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' + +import { Domain } from '../../scalars' +import { logActivity } from '../../audit-logs' +import { headers } from 'nats' + +export const requestScan = new mutationWithClientMutationId({ + name: 'RequestScan', + description: 'This mutation is used to start a manual scan on a requested domain.', + inputFields: () => ({ + domain: { + type: Domain, + description: 'The domain that the scan will be ran on.', + }, + }), + outputFields: () => ({ + status: { + type: GraphQLString, + description: 'Informs the user if the scan was dispatched successfully.', + resolve: ({ status }) => status, + }, + }), + mutateAndGetPayload: async ( + args, + { + query, + collections, + transaction, + i18n, + userKey, + request: { ip }, + publish, + auth: { checkDomainPermission, userRequired, verifiedRequired }, + loaders: { loadDomainByDomain }, + dataSources: { webScan }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Cleanse input + const domainInput = cleanseInput(args.domain) + + // Check to see if domain exists + const domain = await loadDomainByDomain.load(domainInput) + + if (typeof domain === 'undefined') { + console.warn( + `User: ${userKey} attempted to start a one time scan on: ${domainInput} however domain cannot be found.`, + ) + throw new Error(i18n._(t`Unable to request a one time scan on an unknown domain.`)) + } + + // Check to see if user has access to domain + const permission = await checkDomainPermission({ domainId: domain._id }) + + if (!permission) { + console.warn( + `User: ${userKey} attempted to start a one time scan on: ${domain.domain} however they do not have permission to do so.`, + ) + throw new Error( + i18n._(t`Permission Denied: Please contact organization user for help with scanning this domain.`), + ) + } + + let orgsClaimingDomainQuery + try { + orgsClaimingDomainQuery = await query` + WITH domains, users, organizations + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${user._id} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET domainOrgClaims = ( + FOR v, e IN 1..1 ANY ${domain._id} claims + RETURN v + ) + LET orgsClaimingDomain = UNIQUE(domainOrgClaims[* FILTER CURRENT.verified == true || CURRENT IN userAffiliations]) + RETURN orgsClaimingDomain + ` + } catch (err) { + console.error( + `Database error when retrieving organizations claiming domain: ${userKey} and domain: ${domain._id}: ${err}`, + ) + throw new Error(i18n._(t`Error while requesting scan. Please try again.`)) + } + + let orgsClaimingDomain + try { + orgsClaimingDomain = await orgsClaimingDomainQuery.next() + } catch (err) { + console.error( + `Cursor error when retrieving organizations claiming domain: ${userKey} and domain: ${domain._id}: ${err}`, + ) + throw new Error(i18n._(t`Error while requesting scan. Please try again.`)) + } + + // Check to see if a scan is already pending + try { + const webConnections = await webScan.getConnectionsByDomainId({ + domainId: domain._id, + limit: 1, + orderBy: { field: 'timestamp', direction: 'DESC' }, + excludePending: false, + }) + if (webConnections.edges.length > 0) { + const webConnection = webConnections.edges[0].node + const webScans = await webScan.getScansByWebId({ webId: webConnection._id }) + webScans.forEach((result) => { + const timeDifferenceInMinutes = (Date.now() - new Date(webConnection.timestamp).getTime()) / 1000 / 60 + if (result.status.toUpperCase() === 'PENDING' && timeDifferenceInMinutes < 30) { + console.warn( + `User: ${userKey} attempted to start a one time scan on: ${domain.domain} however a scan is already pending.`, + ) + throw new Error(i18n._(t`Unable to request a one time scan on a domain that already has a pending scan.`)) + } + }) + } + } catch (err) { + console.error( + `Error occurred when user: ${userKey} attempted to start a one time scan on: ${domain.domain}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request a one time scan. Please try again.`)) + } + + const hdrs = headers() + hdrs.set('priority', 'high') + + await publish({ + channel: 'scans.requests_priority', + msg: { + domain: domain.domain, + domain_key: domain._key, + hash: domain.hash, + user_key: null, // only used for One Time Scans + shared_id: null, // only used for One Time Scans + }, + options: { + headers: hdrs, + }, + }) + + // Logs scan request activity for each org claiming domain + for (const orgClaimingDomain of orgsClaimingDomain) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'scan', + target: { + resource: domain.domain, + organization: { + id: orgClaimingDomain._key, + name: orgClaimingDomain.orgDetails.en.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }) + } + + console.info(`User: ${userKey} successfully dispatched a one time scan on domain: ${domain.domain}.`) + + return { + status: i18n._(t`Successfully dispatched one time scan.`), + } + }, +}) diff --git a/api/src/domain/mutations/unfavourite-domain.js b/api/src/domain/mutations/unfavourite-domain.js new file mode 100644 index 0000000000..8d8738d94e --- /dev/null +++ b/api/src/domain/mutations/unfavourite-domain.js @@ -0,0 +1,130 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { removeDomainUnion } from '../unions' + +export const unfavouriteDomain = new mutationWithClientMutationId({ + name: 'UnfavouriteDomain', + description: "Mutation to remove domain from user's personal myTracker view.", + inputFields: () => ({ + domainId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the domain you wish to favourite.', + }, + }), + outputFields: () => ({ + result: { + type: new GraphQLNonNull(removeDomainUnion), + description: '`RemoveDomainUnion` returning either a `DomainResultType`, or `DomainErrorType` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + auth: { userRequired, verifiedRequired }, + loaders: { loadDomainByKey }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Cleanse input + const { type: _domainType, id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + + // Get domain from db + const domain = await loadDomainByKey.load(domainId) + // Check to see if domain exists + if (typeof domain === 'undefined') { + console.warn( + `User: ${userKey} attempted to unfavourite ${domainId} however no domain is associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to unfavourite unknown domain.`), + } + } + + // Check to see if domain already favourited by user + let checkDomainCursor + try { + checkDomainCursor = await query` + WITH domains + FOR v, e IN 1..1 ANY ${domain._id} favourites + FILTER e._from == ${user._id} + RETURN e + ` + } catch (err) { + console.error(`Database error occurred while running check to see if domain already favourited: ${err}`) + throw new Error(i18n._(t`Unable to favourite domain. Please try again.`)) + } + + let checkUserDomain + try { + checkUserDomain = await checkDomainCursor.next() + } catch (err) { + console.error(`Cursor error occurred while running check to see if domain already favourited: ${err}`) + throw new Error(i18n._(t`Unable to favourite domain. Please try again.`)) + } + + if (typeof checkUserDomain === 'undefined') { + console.warn(`User: ${userKey} attempted to unfavourite a domain, however domain is not favourited.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to unfavourite domain, domain is not favourited.`), + } + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => + query` + WITH favourites, domains, users + LET domainEdges = (FOR v, e IN 1..1 INBOUND ${domain._id} favourites RETURN { _key: e._key, _from: e._from, _to: e._to }) + LET edgeKeys = ( + FOR domainEdge IN domainEdges + FILTER domainEdge._to == ${domain._id} + FILTER domainEdge._from == ${user._id} + RETURN domainEdge._key + ) + FOR edgeKey IN edgeKeys + REMOVE edgeKey IN favourites + OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${userKey} when removing domain edge: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to unfavourite domain. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userKey} was unfavouriting domain: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to unfavourite domain. Please try again.`)) + } + + console.info(`User: ${userKey} successfully removed domain ${domain.domain} from favourites.`) + + return { + _type: 'result', + status: i18n._(t`Successfully removed domain: ${domain.domain} from favourites.`), + domain, + } + }, +}) diff --git a/api/src/domain/mutations/unignore-cve.js b/api/src/domain/mutations/unignore-cve.js new file mode 100644 index 0000000000..b4f82f4cd1 --- /dev/null +++ b/api/src/domain/mutations/unignore-cve.js @@ -0,0 +1,233 @@ +import { GraphQLID, GraphQLNonNull } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { CveID } from '../../scalars' +import { ignoreCveUnion } from '../unions/ignore-cve-union' +import { logActivity } from '../../audit-logs' + +export const unignoreCve = new mutationWithClientMutationId({ + name: 'UnignoreCve', + description: 'Unignore a CVE for a domain.', + inputFields: () => ({ + domainId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the domain which is unignoring the CVE.', + }, + ignoredCve: { + description: 'The CVE ID that is being ignored.', + type: CveID, + }, + }), + outputFields: () => ({ + result: { + type: ignoreCveUnion, + description: '`IgnoreCveUnion` returning either a `Domain` or an error', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { userRequired, checkSuperAdmin, superAdminRequired, verifiedRequired, tfaRequired }, + validators: { cleanseInput }, + loaders: { loadDomainByKey }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + tfaRequired({ user }) + + // Only super admins can ignore CVEs + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + const { id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + + const ignoredCve = cleanseInput(args.ignoredCve) + + // Check to see if domain exists + const domain = await loadDomainByKey.load(domainId) + + if (typeof domain === 'undefined') { + console.warn(`User: "${userKey}" attempted to unignore CVE "${ignoredCve}" on unknown domain: "${domainId}".`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to stop ignoring CVE. Please try again.`), + } + } + + const oldIgnoredCves = domain.ignoredCves + + if (!oldIgnoredCves.includes(ignoredCve)) { + console.warn( + `User: "${userKey}" attempted to unignore CVE "${ignoredCve}" on domain: "${domainId}" however CVE is not ignored.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`CVE is not ignored for this domain.`), + } + } + + const newIgnoredCves = Array.from(new Set([...oldIgnoredCves.filter((cve) => cve !== ignoredCve)])) + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + async () => + await query` + UPSERT { _key: ${domain._key} } + INSERT ${{ ignoredCves: newIgnoredCves }} + UPDATE ${{ ignoredCves: newIgnoredCves }} + IN domains + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: "${userKey}" attempted to unignore CVE "${ignoredCve}" on domain "${domainId}", error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to stop ignoring CVE. Please try again.`)) + } + + let currentDomainVulnerabilitiesCursor + try { + currentDomainVulnerabilitiesCursor = await trx.step( + () => query` + FOR finding IN additionalFindings + FILTER finding.domain == ${domain._id} + LIMIT 1 + FOR wc IN finding.webComponents + FILTER LENGTH(wc.WebComponentCves) > 0 + FOR vuln IN wc.WebComponentCves + FILTER vuln.Cve NOT IN ${newIgnoredCves} + RETURN DISTINCT vuln.Cve + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: "${userKey}" attempted to unignore CVE "${ignoredCve}" on domain "${domainId}" when getting current CVEs, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to stop ignoring CVE. Please try again.`)) + } + + try { + await trx.step( + () => + query` + UPDATE { _key: ${domain._key}, cveDetected: ${currentDomainVulnerabilitiesCursor.count > 0} } IN domains + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: "${userKey}" attempted to unignore CVE "${ignoredCve}" on domain "${domainId}" when updating domain, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to stop ignoring CVE. Please try again.`)) + } + + // Commit transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred when user: "${userKey}" attempted to unignore CVE "${ignoredCve}" on domain "${domainId}", error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to stop ignoring CVE. Please try again.`)) + } + + // Get all verified claims to domain and activityLog those organizations + try { + const orgs = await query` + FOR v, e IN 1..1 INBOUND ${domain._id} claims + FILTER v.verified == true + RETURN { + _key: v._key, + name: v.orgDetails.en.orgName, + } + ` + for await (const org of orgs) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: 'super_admin', + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain.domain, + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + updatedProperties: [ + { + name: ignoredCve, + oldValue: 'ignored', + newValue: 'unignored', + }, + ], + }, + }) + } + // Log activity for super admin logging + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: 'super_admin', + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain.domain, + resourceType: 'domain', + updatedProperties: [ + { + name: ignoredCve, + oldValue: 'ignored', + newValue: 'unignored', + }, + ], + }, + }) + } catch (err) { + console.error( + `Database error occurred when user: "${userKey}" attempted to unignore CVE "${ignoredCve}" on domain "${domainId}" during activity logs, error: ${err}`, + ) + } + + // Clear dataloader and load updated domain + await loadDomainByKey.clear(domain._key) + const returnDomain = await loadDomainByKey.load(domain._key) + + console.info(`User: "${userKey}" successfully unignored CVE "${ignoredCve}" on domain: "${domainId}".`) + + returnDomain.id = returnDomain._key + + return { + ...returnDomain, + } + }, +}) diff --git a/api/src/domain/mutations/update-domain.js b/api/src/domain/mutations/update-domain.js new file mode 100644 index 0000000000..a0ff59ea96 --- /dev/null +++ b/api/src/domain/mutations/update-domain.js @@ -0,0 +1,358 @@ +import { GraphQLID, GraphQLNonNull, GraphQLList, GraphQLBoolean, GraphQLString } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { updateDomainUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { AssetStateEnums } from '../../enums' +import { CvdEnrollmentInputOptions } from '../../additional-findings/input/cvd-enrollment-options' +import ac from '../../access-control' + +export const updateDomain = new mutationWithClientMutationId({ + name: 'UpdateDomain', + description: 'Mutation allows the modification of domains if domain is updated through out its life-cycle', + inputFields: () => ({ + domainId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the domain that is being updated.', + }, + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global ID of the organization used for permission checks.', + }, + tags: { + description: 'List of labelled tags users have applied to the domain.', + type: new GraphQLList(GraphQLString), + }, + archived: { + description: 'Value that determines if the domain is excluded from the scanning process.', + type: GraphQLBoolean, + }, + ignoreRua: { + description: 'Boolean value that determines if the domain should ignore rua reports.', + type: GraphQLBoolean, + }, + assetState: { + description: 'Value that determines how the domain relates to the organization.', + type: AssetStateEnums, + }, + cvdEnrollment: { + description: + 'The Coordinated Vulnerability Disclosure (CVD) enrollment details for this domain, including HackerOne integration status and CVSS requirements.', + type: CvdEnrollmentInputOptions, + }, + }), + outputFields: () => ({ + result: { + type: updateDomainUnion, + description: '`UpdateDomainUnion` returning either a `Domain`, or `DomainError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + validators: { cleanseInput }, + loaders: { loadDomainByKey, loadOrgByKey, loadTagByTagId }, + }, + ) => { + // Get User + const user = await userRequired() + + verifiedRequired({ user }) + tfaRequired({ user }) + + const { id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + let tags + if (typeof args.tags !== 'undefined') { + tags = await loadTagByTagId.loadMany( + args.tags.map((tag) => { + return cleanseInput(tag) + }), + ) + tags = tags + .filter(({ visible, ownership, organizations }) => { + // Filter out tags that are not visible or do not belong to the org + return visible && (ownership === 'global' || organizations.some((org) => org === orgId)) + }) + .map((tag) => tag.tagId) + } else { + tags = null + } + + let archived + if (typeof args.archived !== 'undefined') { + archived = args.archived + } else { + archived = null + } + + let assetState + if (typeof args.assetState !== 'undefined') { + assetState = cleanseInput(args.assetState) + } else { + assetState = null + } + + let cvdEnrollment + if (typeof args.cvdEnrollment !== 'undefined') { + cvdEnrollment = args.cvdEnrollment + } else { + cvdEnrollment = null + } + + // Check to see if domain exists + const domain = await loadDomainByKey.load(domainId) + + if (typeof domain === 'undefined') { + console.warn( + `User: ${userKey} attempted to update domain: ${domainId}, however there is no domain associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update unknown domain.`), + } + } + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however there is no org associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update domain in an unknown org.`), + } + } + + // Check permission + const permission = await checkPermission({ orgId: org._id }) + + if (!ac.can(permission).updateOwn('domain').granted) { + console.warn( + `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however they do not have permission in that org.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact organization user for help with updating this domain.`), + } + } + + // Check to see if org has a claim to this domain + let countCursor + try { + countCursor = await query` + WITH claims, domains, organizations + FOR v, e IN 1..1 ANY ${domain._id} claims + FILTER e._from == ${org._id} + RETURN e + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} attempted to update domain: ${domainId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to update domain. Please try again.`)) + } + + if (countCursor.count < 1) { + console.warn( + `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however that org has no claims to that domain.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update domain that does not belong to the given organization.`), + } + } + + if ( + !ac.can(permission).updateOwn('cvd-enrollment').granted && + ['enrolled', 'deny'].includes(cvdEnrollment?.status) + ) { + console.warn( + `User: ${userKey} attempted to update the CVD enrollment for domain: ${domainId} in org: ${orgId}, however they do not have permission in that org.`, + ) + cvdEnrollment.status = cvdEnrollment.status === 'enrolled' ? 'pending' : 'not-enrolled' + } + + // Setup Transaction + const trx = await transaction(collections) + + // Update domain + const domainToInsert = { + archived: typeof archived !== 'undefined' ? archived : domain?.archived, + ignoreRua: typeof args.ignoreRua !== 'undefined' ? args.ignoreRua : domain?.ignoreRua, + cvdEnrollment: typeof cvdEnrollment !== 'undefined' ? cvdEnrollment : domain?.cvdEnrollment, + } + + try { + await trx.step( + async () => + await query` + WITH domains + UPSERT { _key: ${domain._key} } + INSERT ${domainToInsert} + UPDATE ${domainToInsert} + IN domains + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: ${userKey} attempted to update domain: ${domainId}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to update domain. Please try again.`)) + } + + let claimCursor + try { + claimCursor = await query` + WITH claims + FOR claim IN claims + FILTER claim._from == ${org._id} && claim._to == ${domain._id} + RETURN MERGE({ id: claim._key, _type: "claim" }, claim) + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadDomainByKey: ${err}`) + } + let claim + try { + claim = await claimCursor.next() + } catch (err) { + console.error(`Cursor error occurred when user: ${userKey} running loadDomainByKey: ${err}`) + } + + const claimToInsert = { + tags: tags || claim?.tags, + firstSeen: typeof claim?.firstSeen === 'undefined' ? new Date().toISOString() : claim?.firstSeen, + assetState: assetState || claim?.assetState, + } + + try { + await trx.step( + async () => + await query` + WITH claims + UPSERT { _from: ${org._id}, _to: ${domain._id} } + INSERT ${claimToInsert} + UPDATE ${claimToInsert} + IN claims + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: ${userKey} attempted to update domain edge, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to update domain edge. Please try again.`)) + } + + // Commit transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred when user: ${userKey} attempted to update domain: ${domainId}, error: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to update domain. Please try again.`)) + } + + // Clear dataloader and load updated domain + await loadDomainByKey.clear(domain._key) + const returnDomain = await loadDomainByKey.load(domain._key) + + console.info(`User: ${userKey} successfully updated domain: ${domainId}.`) + + const updatedProperties = [] + if (typeof assetState !== 'undefined' && assetState !== claim.assetState) { + updatedProperties.push({ + name: 'assetState', + oldValue: claim.assetState, + newValue: assetState, + }) + } + + if (typeof cvdEnrollment !== 'undefined' && cvdEnrollment?.status !== domain?.cvdEnrollment?.status) { + updatedProperties.push({ + name: 'cvdEnrollment', + oldValue: JSON.stringify(domain.cvdEnrollment?.status), + newValue: JSON.stringify(cvdEnrollment.status), + }) + } + + if (JSON.stringify(claim.tags) !== JSON.stringify(claimToInsert.tags)) { + updatedProperties.push({ + name: 'tags', + oldValue: claim.tags, + newValue: claimToInsert.tags, + }) + } + + if (updatedProperties.length > 0) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain.domain, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + updatedProperties, + }, + }) + } + + if (typeof archived !== 'undefined') { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain.domain, + resourceType: 'domain', // user, org, domain + updatedProperties: [{ name: 'archived', oldValue: domain.archived, newValue: archived }], + }, + }) + } + + returnDomain.id = returnDomain._key + + return { + ...returnDomain, + claimTags: claimToInsert.tags, + assetState, + } + }, +}) diff --git a/api/src/domain/mutations/update-domains-by-domain-ids.js b/api/src/domain/mutations/update-domains-by-domain-ids.js new file mode 100644 index 0000000000..17d0fad6c2 --- /dev/null +++ b/api/src/domain/mutations/update-domains-by-domain-ids.js @@ -0,0 +1,182 @@ +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { bulkModifyDomainsUnion } from '../unions' +import { GraphQLID, GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql' +import { t } from '@lingui/macro' +import { logActivity } from '../../audit-logs' +import ac from '../../access-control' + +export const updateDomainsByDomainIds = new mutationWithClientMutationId({ + name: 'UpdateDomainsByDomainIds', + description: '', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish to assign this domain to.', + }, + tags: { + type: new GraphQLNonNull(new GraphQLList(GraphQLString)), + description: 'List of labelled tags users have applied to the domain.', + }, + domainIds: { + type: new GraphQLList(GraphQLID), + description: '', + }, + }), + outputFields: () => ({ + result: { + type: bulkModifyDomainsUnion, + description: '`BulkModifyDomainsUnion` returning either a `DomainBulkResult`, or `DomainErrorType` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadTagByTagId, loadOrgByKey }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + tfaRequired({ user }) + + // Cleanse input + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + let tags = (await loadTagByTagId.loadMany(args.tags.map((tag) => cleanseInput(tag)))) ?? [] + tags = tags + .filter( + ({ visible, ownership, organizations }) => + visible && (ownership === 'global' || organizations.some((org) => org === orgId)), + ) + .map((tag) => tag.tagId) + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + if (typeof org === 'undefined') { + console.warn(`User: ${userKey} attempted to update domains to an organization: ${orgId} that does not exist.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update domains in unknown organization.`), + } + } + + // Check to see if user belongs to org + const permission = await checkPermission({ orgId: org._id }) + if (!ac.can(permission).updateOwn('domain').granted) { + console.warn( + `User: ${userKey} attempted to update domains in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with updating domains.`), + } + } + + let domainCount = 0 + const orgKeyString = `organizations/${orgId}` + for (const id of args.domainIds) { + const { id: domainId } = fromGlobalId(cleanseInput(id)) + // check for valid domain/claim + let checkClaimCursor + try { + checkClaimCursor = await query` + WITH claims, domains, organizations + FOR v, e IN 1..1 ANY ${orgKeyString} claims + FILTER v._key == ${domainId} + RETURN { claim: e, domain: v.domain } + ` + } catch (err) { + console.error(`Database error occurred while running check to see if domain already exists in an org: ${err}`) + continue + } + + let checkClaim + try { + checkClaim = await checkClaimCursor.next() + } catch (err) { + console.error(`Cursor error occurred while running check to see if domain already exists in an org: ${err}`) + continue + } + + if (typeof checkClaim === 'undefined') { + console.warn( + `User: ${userKey} attempted to update a domain for: ${org.slug}, however that org does not have that domain claimed.`, + ) + continue + } + + // Setup Transaction + const trx = await transaction(collections) + const { claim, domain } = checkClaim + const claimToInsert = { + tags: [...new Set([...claim.tags, ...tags])], + } + + try { + await trx.step( + async () => + await query` + WITH claims + UPSERT { _key: ${claim._key} } + INSERT ${claimToInsert} + UPDATE ${claimToInsert} + IN claims + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: ${userKey} attempted to update domain edge, error: ${err}`, + ) + await trx.abort() + continue + } + + // commit and log + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userKey} was creating domains: ${err}`) + await trx.abort() + continue + } + + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain, + updatedProperties: [{ name: 'tags', oldValue: claim.tags, newValue: claimToInsert.tags }], + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + domainCount++ + } + + return { + _type: 'result', + status: i18n._(t`Successfully updated ${domainCount} domain(s) in ${org.slug} with ${tags.join(', ')}.`), + } + }, +}) diff --git a/api/src/domain/mutations/update-domains-by-filters.js b/api/src/domain/mutations/update-domains-by-filters.js new file mode 100644 index 0000000000..743d7a7950 --- /dev/null +++ b/api/src/domain/mutations/update-domains-by-filters.js @@ -0,0 +1,313 @@ +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { bulkModifyDomainsUnion } from '../unions' +import { GraphQLID, GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql' +import { t } from '@lingui/macro' +import { logActivity } from '../../audit-logs' +import { domainFilter } from '../inputs' +import { aql } from 'arangojs' +import ac from '../../access-control' + +export const updateDomainsByFilters = new mutationWithClientMutationId({ + name: 'UpdateDomainsByFilters', + description: '', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish to assign this domain to.', + }, + tags: { + type: new GraphQLNonNull(new GraphQLList(GraphQLString)), + description: 'List of labelled tags users have applied to the domain.', + }, + filters: { + type: new GraphQLList(domainFilter), + description: '', + }, + search: { + type: GraphQLString, + description: '', + }, + }), + outputFields: () => ({ + result: { + type: bulkModifyDomainsUnion, + description: '`BulkModifyDomainsUnion` returning either a `DomainBulkResult`, or `DomainErrorType` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadTagByTagId, loadOrgByKey }, + validators: { cleanseInput }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + tfaRequired({ user }) + + // Cleanse input + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + const search = cleanseInput(args.search) + let tags = (await loadTagByTagId.loadMany(args.tags.map((tag) => cleanseInput(tag)))) ?? [] + tags = tags + .filter( + ({ visible, ownership, organizations }) => + visible && (ownership === 'global' || organizations.some((org) => org === orgId)), + ) + .map((tag) => tag.tagId) + const filters = args.filters + + // Check to see if org exists + const org = await loadOrgByKey.load(orgId) + if (typeof org === 'undefined') { + console.warn(`User: ${userKey} attempted to update domains to an organization: ${orgId} that does not exist.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update domains in unknown organization.`), + } + } + + // Check to see if user belongs to org + const permission = await checkPermission({ orgId: org._id }) + if (!ac.can(permission).updateOwn('domain').granted) { + console.warn( + `User: ${userKey} attempted to update domains in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with updating domains.`), + } + } + + const orgKeyString = `organizations/${orgId}` + let domainFilters = aql`` + if (typeof filters !== 'undefined') { + filters.forEach(({ filterCategory, comparison, filterValue }) => { + if (comparison === '==') { + comparison = aql`==` + } else { + comparison = aql`!=` + } + if (filterCategory === 'dmarc-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.dmarc ${comparison} ${filterValue} + ` + } else if (filterCategory === 'dkim-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.dkim ${comparison} ${filterValue} + ` + } else if (filterCategory === 'https-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.https ${comparison} ${filterValue} + ` + } else if (filterCategory === 'spf-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.spf ${comparison} ${filterValue} + ` + } else if (filterCategory === 'ciphers-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.ciphers ${comparison} ${filterValue} + ` + } else if (filterCategory === 'curves-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.curves ${comparison} ${filterValue} + ` + } else if (filterCategory === 'hsts-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.hsts ${comparison} ${filterValue} + ` + } else if (filterCategory === 'policy-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.policy ${comparison} ${filterValue} + ` + } else if (filterCategory === 'protocols-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.protocols ${comparison} ${filterValue} + ` + } else if (filterCategory === 'certificates-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.certificates ${comparison} ${filterValue} + ` + } else if (filterCategory === 'tags') { + if (filterValue === 'archived') { + domainFilters = aql` + ${domainFilters} + FILTER v.archived ${comparison} true + ` + } else if (filterValue === 'nxdomain') { + domainFilters = aql` + ${domainFilters} + FILTER v.rcode ${comparison} "NXDOMAIN" + ` + } else if (filterValue === 'blocked') { + domainFilters = aql` + ${domainFilters} + FILTER v.blocked ${comparison} true + ` + } else if (filterValue === 'wildcard-sibling') { + domainFilters = aql` + ${domainFilters} + FILTER v.wildcardSibling ${comparison} true + ` + } else if (filterValue === 'wildcard-entry') { + domainFilters = aql` + ${domainFilters} + FILTER v.wildcardEntry ${comparison} true + ` + } else if (filterValue === 'scan-pending') { + domainFilters = aql` + ${domainFilters} + FILTER v.webScanPending ${comparison} true + ` + } else if (filterValue === 'has-entrust-certificate') { + domainFilters = aql` + ${domainFilters} + FILTER v.hasEntrustCertificate ${comparison} true + ` + } else if (filterValue === 'cve-detected') { + domainFilters = aql` + ${domainFilters} + FILTER v.cveDetected ${comparison} true + ` + } else { + domainFilters = aql` + ${domainFilters} + FILTER POSITION( e.tags, ${filterValue}) ${comparison} true + ` + } + } else if (filterCategory === 'asset-state') { + domainFilters = aql` + ${domainFilters} + FILTER e.assetState ${comparison} ${filterValue} + ` + } else if (filterCategory === 'guidance-tag') { + domainFilters = aql` + ${domainFilters} + FILTER POSITION(negativeTags, ${filterValue}) ${comparison} true + ` + } + }) + } + + let searchString = aql`` + if (typeof search !== 'undefined' && search !== '') { + searchString = aql`FILTER LOWER(v.domain) LIKE LOWER(${search})` + } + + let checkClaimsCursor + try { + checkClaimsCursor = await query` + WITH claims, domains, organizations + FOR v, e IN 1..1 ANY ${orgKeyString} claims + ${domainFilters} + ${searchString} + RETURN { claim: e, domain: v.domain } + ` + } catch (err) { + console.error(`Database error occurred while running check to see if domain already exists in an org: ${err}`) + throw new Error(i18n._(t`Unable to update domains. Please try again.`)) + } + + let checkClaims + try { + checkClaims = await checkClaimsCursor.all() + } catch (err) { + console.error(`Cursor error occurred while running check to see if domain already exists in an org: ${err}`) + throw new Error(i18n._(t`Unable to update domains. Please try again.`)) + } + + if (typeof checkClaims === 'undefined') { + console.warn( + `User: ${userKey} attempted to update a domain for: ${org.slug}, however that org does not have that domain claimed.`, + ) + throw new Error(i18n._(t`Unable to update domains. Please try again.`)) + } + + let domainCount = 0 + for (const checkClaim of checkClaims) { + // Setup Transaction + const trx = await transaction(collections) + const { claim, domain } = checkClaim + const claimToInsert = { + tags: [...new Set([...claim.tags, ...tags])], + } + + try { + await trx.step( + async () => + await query` + WITH claims + UPSERT { _key: ${claim._key} } + INSERT ${claimToInsert} + UPDATE ${claimToInsert} + IN claims + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: ${userKey} attempted to update domain edge, error: ${err}`, + ) + await trx.abort() + continue + } + + // commit and log + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userKey} was creating domains: ${err}`) + await trx.abort() + continue + } + + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: domain, + updatedProperties: [{ name: 'tags', oldValue: claim.tags, newValue: claimToInsert.tags }], + organization: { + id: org._key, + name: org.name, + }, + resourceType: 'domain', + }, + }) + domainCount++ + } + + return { + _type: 'result', + status: i18n._(t`Successfully updated ${domainCount} domain(s) in ${org.slug} with ${tags.join(', ')}.`), + } + }, +}) diff --git a/api-js/src/domain/objects/__tests__/domain-connection.test.js b/api/src/domain/objects/__tests__/domain-connection.test.js similarity index 78% rename from api-js/src/domain/objects/__tests__/domain-connection.test.js rename to api/src/domain/objects/__tests__/domain-connection.test.js index 93732ade59..d999b49398 100644 --- a/api-js/src/domain/objects/__tests__/domain-connection.test.js +++ b/api/src/domain/objects/__tests__/domain-connection.test.js @@ -1,5 +1,5 @@ -import { GraphQLInt } from 'graphql' -import { domainConnection } from '../domain-connection' +import {GraphQLInt} from 'graphql' +import {domainConnection} from '../domain-connection' describe('given the domain connection object', () => { describe('testing its field definitions', () => { @@ -15,7 +15,7 @@ describe('given the domain connection object', () => { it('returns the resolved value', () => { const demoType = domainConnection.connectionType.getFields() - expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) + expect(demoType.totalCount.resolve({totalCount: 1})).toEqual(1) }) }) }) diff --git a/api-js/src/domain/objects/__tests__/domain-error.test.js b/api/src/domain/objects/__tests__/domain-error.test.js similarity index 80% rename from api-js/src/domain/objects/__tests__/domain-error.test.js rename to api/src/domain/objects/__tests__/domain-error.test.js index bd29ca65aa..dc15c23025 100644 --- a/api-js/src/domain/objects/__tests__/domain-error.test.js +++ b/api/src/domain/objects/__tests__/domain-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { domainErrorType } from '../domain-error' +import {domainErrorType} from '../domain-error' describe('given the domainErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the domainErrorType object', () => { it('returns the resolved field', () => { const demoType = domainErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the domainErrorType object', () => { const demoType = domainErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api/src/domain/objects/__tests__/domain-result.test.js b/api/src/domain/objects/__tests__/domain-result.test.js new file mode 100644 index 0000000000..7a02d582a2 --- /dev/null +++ b/api/src/domain/objects/__tests__/domain-result.test.js @@ -0,0 +1,38 @@ +import {GraphQLString} from 'graphql' + +import {domainResultType} from '../domain-result' +import {domainType} from '../domain' + +describe('given the domainResultType object', () => { + describe('testing the field definitions', () => { + it('has an status field', () => { + const demoType = domainResultType.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + it('has a domain type', () => { + const demoType = domainResultType.getFields() + + expect(demoType).toHaveProperty('domain') + expect(demoType.domain.type).toMatchObject(domainType) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = domainResultType.getFields() + + expect(demoType.status.resolve({status: 'status'})).toEqual('status') + }) + }) + describe('testing the domain resolver', () => { + const demoType = domainResultType.getFields() + + expect( + demoType.domain.resolve({domain: {id: 1, domain: 'test.gc.ca'}}), + ).toEqual({id: 1, domain: 'test.gc.ca'}) + }) + }) +}) diff --git a/api/src/domain/objects/__tests__/domain-status.test.js b/api/src/domain/objects/__tests__/domain-status.test.js new file mode 100644 index 0000000000..3bff2bd283 --- /dev/null +++ b/api/src/domain/objects/__tests__/domain-status.test.js @@ -0,0 +1,202 @@ +import {StatusEnum} from '../../../enums' +import {domainStatus} from '../domain-status' + +describe('given the domainStatus object', () => { + describe('testing its field definitions', () => { + it('has a ciphers field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('ciphers') + expect(demoType.ciphers.type).toMatchObject(StatusEnum) + }) + it('has a curves field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('curves') + expect(demoType.curves.type).toMatchObject(StatusEnum) + }) + it('has a dkim field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('dkim') + expect(demoType.dkim.type).toMatchObject(StatusEnum) + }) + it('has a dmarc field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('dmarc') + expect(demoType.dmarc.type).toMatchObject(StatusEnum) + }) + it('has a https field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('https') + expect(demoType.https.type).toMatchObject(StatusEnum) + }) + it('has a hsts field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('hsts') + expect(demoType.hsts.type).toMatchObject(StatusEnum) + }) + it('has a policy field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('policy') + expect(demoType.policy.type).toMatchObject(StatusEnum) + }) + it('has a protocols field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('protocols') + expect(demoType.protocols.type).toMatchObject(StatusEnum) + }) + it('has a spf field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('spf') + expect(demoType.spf.type).toMatchObject(StatusEnum) + }) + it('has a ssl field', () => { + const demoType = domainStatus.getFields() + + expect(demoType).toHaveProperty('ssl') + expect(demoType.ssl.type).toMatchObject(StatusEnum) + }) + }) + + describe('testing its field resolvers', () => { + describe('testing the ciphers resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.ciphers.resolve({ciphers: 'pass'})).toEqual('pass') + }) + }) + describe('testing the curves resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.curves.resolve({curves: 'pass'})).toEqual('pass') + }) + }) + describe('testing the dkim resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.dkim.resolve({dkim: 'pass'})).toEqual('pass') + }) + }) + describe('testing the dmarc resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.dmarc.resolve({dmarc: 'pass'})).toEqual('pass') + }) + }) + describe('testing the https resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.https.resolve({https: 'pass'})).toEqual('pass') + }) + }) + describe('testing the hsts resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.hsts.resolve({hsts: 'pass'})).toEqual('pass') + }) + }) + + describe('testing the policy resolver', () => { + it('sets the policy field to pass if every component passes', () => { + const demoType = domainStatus.getFields() + const fields = { + ciphers: 'pass', + https: 'pass', + hsts: 'pass', + protocols: 'pass', + ssl: 'pass', + } + // All pass so policy passes + expect(demoType.policy.resolve(fields)).toEqual('pass') + + // One fails so policy fails + Object.keys(fields).forEach((k) => { + const mutatedFields = Object.assign({}, fields) + mutatedFields[k] = 'fail' + expect(demoType.policy.resolve(mutatedFields)).toEqual('fail') + }) + }) + }) + + describe('testing the policy resolver', () => { + it('sets the policy field to pass if every component is info', () => { + const demoType = domainStatus.getFields() + const fields = { + ciphers: 'info', + https: 'info', + hsts: 'info', + protocols: 'info', + ssl: 'info', + } + // All info so policy passes + expect(demoType.policy.resolve(fields)).toEqual('pass') + + // One fails so policy fails + Object.keys(fields).forEach((k) => { + const mutatedFields = Object.assign({}, fields) + mutatedFields[k] = 'fail' + expect(demoType.policy.resolve(mutatedFields)).toEqual('fail') + }) + }) + }) + + describe('testing the policy resolver', () => { + it('returns fail if any field fails', () => { + const demoType = domainStatus.getFields() + const fields = { + ciphers: 'info', + https: 'info', + hsts: 'fail', + protocols: 'info', + ssl: 'info', + } + // All info so policy passes + expect(demoType.policy.resolve(fields)).toEqual('fail') + + // One fails so policy fails + Object.keys(fields).forEach((k) => { + const mutatedFields = Object.assign({}, fields) + mutatedFields[k] = 'fail' + expect(demoType.policy.resolve(mutatedFields)).toEqual('fail') + }) + }) + }) + + describe('testing the protocols resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.protocols.resolve({protocols: 'pass'})).toEqual( + 'pass', + ) + }) + }) + describe('testing the spf resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.spf.resolve({spf: 'pass'})).toEqual('pass') + }) + }) + describe('testing the ssl resolver', () => { + it('returns the resolved value', () => { + const demoType = domainStatus.getFields() + + expect(demoType.ssl.resolve({ssl: 'pass'})).toEqual('pass') + }) + }) + }) +}) diff --git a/api/src/domain/objects/__tests__/domain.test.js b/api/src/domain/objects/__tests__/domain.test.js new file mode 100644 index 0000000000..b36bc258dd --- /dev/null +++ b/api/src/domain/objects/__tests__/domain.test.js @@ -0,0 +1,658 @@ +import { GraphQLNonNull, GraphQLID, GraphQLList, GraphQLString, GraphQLBoolean } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import { tokenize } from '../../../auth' +import { organizationConnection } from '../../../organization' +import { domainStatus } from '../domain-status' +import { dmarcSummaryType } from '../../../dmarc-summaries' +import { webConnection } from '../../../web-scan' +import { domainType } from '../../index' +import { Domain, Selectors } from '../../../scalars' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { dnsScanConnection } from '../../../dns-scan' +import { DmarcPhaseEnum } from '../../../enums' + +describe('given the domain object', () => { + describe('testing its field definitions', () => { + it('has an id field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('id') + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) + }) + it('has a domain field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('domain') + expect(demoType.domain.type).toMatchObject(Domain) + }) + it('has a dmarcPhase field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('dmarcPhase') + expect(demoType.dmarcPhase.type).toMatchObject(DmarcPhaseEnum) + }) + it('has a hasDMARCReport field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('hasDMARCReport') + expect(demoType.hasDMARCReport.type).toMatchObject(GraphQLBoolean) + }) + it('has a lastRan field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('lastRan') + expect(demoType.lastRan.type).toMatchObject(GraphQLString) + }) + it('has a selectors field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('selectors') + expect(demoType.selectors.type).toMatchObject(new GraphQLList(Selectors)) + }) + it('has a status field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(domainStatus) + }) + it('has an organizations field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('organizations') + expect(demoType.organizations.type).toMatchObject(organizationConnection.connectionType) + }) + it('has an dnsScan field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('dnsScan') + expect(demoType.dnsScan.type).toMatchObject(dnsScanConnection.connectionType) + }) + it('has a web field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('web') + expect(demoType.web.type).toMatchObject(webConnection.connectionType) + }) + it('has a dmarcSummaryByPeriod field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('dmarcSummaryByPeriod') + expect(demoType.dmarcSummaryByPeriod.type).toMatchObject(dmarcSummaryType) + }) + it('has a yearlyDmarcSummaries field', () => { + const demoType = domainType.getFields() + + expect(demoType).toHaveProperty('yearlyDmarcSummaries') + expect(demoType.yearlyDmarcSummaries.type).toMatchObject(new GraphQLList(dmarcSummaryType)) + }) + it('has a cvdEnrollment field', () => { + const demoType = domainType.getFields() + expect(demoType).toHaveProperty('cvdEnrollment') + }) + }) + describe('testing the field resolvers', () => { + const consoleOutput = [] + const mockedWarn = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.warn = mockedWarn + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('testing the cvdEnrollment resolver', () => { + it('returns the resolved value with correct structure when user is authenticated', async () => { + const demoType = domainType.getFields() + const mockUserRequired = jest.fn() + const cvdEnrollmentValue = { + status: 'ENROLLED', + description: 'Test asset', + maxSeverity: 'HIGH', + confidentialityRequirement: 'HIGH', + integrityRequirement: 'LOW', + availabilityRequirement: 'LOW', + } + + await expect( + demoType.cvdEnrollment.resolve( + { cvdEnrollment: cvdEnrollmentValue }, + {}, + { auth: { userRequired: mockUserRequired } }, + ), + ).resolves.toEqual(cvdEnrollmentValue) + expect(mockUserRequired).toHaveBeenCalled() + }) + it('returns undefined if cvdEnrollment is not present', async () => { + const demoType = domainType.getFields() + const mockUserRequired = jest.fn() + await expect( + demoType.cvdEnrollment.resolve({}, {}, { auth: { userRequired: mockUserRequired } }), + ).resolves.toBeUndefined() + }) + }) + + describe('testing the id resolver', () => { + it('returns the resolved value', () => { + const demoType = domainType.getFields() + + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('domain', 1)) + }) + }) + describe('testing the domain resolver', () => { + it('returns the resolved value', () => { + const demoType = domainType.getFields() + + expect(demoType.domain.resolve({ domain: 'test.gc.ca' })).toEqual('test.gc.ca') + }) + }) + describe('testing the dmarcPhase resolver', () => { + it('returns the resolved value', () => { + const demoType = domainType.getFields() + + expect(demoType.dmarcPhase.resolve({ phase: 'not implemented' })).toEqual('not implemented') + }) + }) + describe('testing the hasDMARCReport resolver', () => { + describe('user has access to dmarc reports', () => { + it('returns true', async () => { + const demoType = domainType.getFields() + + const mockedUserRequired = jest.fn().mockReturnValue(true) + const mockedCheckOwnership = jest.fn().mockReturnValue(true) + + await expect( + demoType.hasDMARCReport.resolve( + { _id: 1 }, + {}, + { + auth: { + checkDomainOwnership: mockedCheckOwnership, + userRequired: mockedUserRequired, + }, + }, + ), + ).resolves.toEqual(true) + }) + }) + describe('user does not have access to dmarc reports', () => { + it('returns false', async () => { + const demoType = domainType.getFields() + + const mockedUserRequired = jest.fn().mockReturnValue(true) + const mockedCheckOwnership = jest.fn().mockReturnValue(false) + + await expect( + demoType.hasDMARCReport.resolve( + { _id: 1 }, + {}, + { + auth: { + checkDomainOwnership: mockedCheckOwnership, + userRequired: mockedUserRequired, + }, + }, + ), + ).resolves.toEqual(false) + }) + }) + }) + describe('testing the lastRan resolver', () => { + it('returns the resolved value', () => { + const demoType = domainType.getFields() + + expect(demoType.lastRan.resolve({ lastRan: '2020-10-02T12:43:39Z' })).toEqual('2020-10-02T12:43:39Z') + }) + }) + describe('testing the selectors resolver', () => { + it('returns the resolved value', () => { + const demoType = domainType.getFields() + + const selectors = ['selector1', 'selector2'] + + expect( + demoType.selectors.resolve( + { _id: 'domains/1', selectors }, + {}, + { + auth: { + userRequired: jest.fn().mockReturnValue(true), + }, + dataSources: { + auth: { domainPermissionByDomainId: { load: jest.fn().mockResolvedValue(true) } }, + }, + loaders: { + loadDkimSelectorsByDomainId: jest.fn().mockReturnValue(selectors), + }, + }, + ), + ).resolves.toEqual(['selector1', 'selector2']) + }) + }) + describe('testing the status resolver', () => { + it('returns the resolved value', () => { + const demoType = domainType.getFields() + + const status = { + dkim: 'pass', + dmarc: 'pass', + https: 'info', + spf: 'fail', + ssl: 'fail', + } + + expect(demoType.status.resolve({ status })).toEqual({ + dkim: 'pass', + dmarc: 'pass', + https: 'info', + spf: 'fail', + ssl: 'fail', + }) + }) + }) + describe('testing the organizations resolver', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const expectedResult = { + edges: [ + { + cursor: toGlobalId('organizations', '1'), + node: { + _id: 'organizations/`', + _key: '1', + _rev: 'rev', + _type: 'organization', + id: '1', + verified: true, + summaries: { + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }, + domainCount: 2, + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + ], + totalCount: 1, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('organizations', '1'), + endCursor: toGlobalId('organizations', '1'), + }, + } + + await expect( + demoType.organizations.resolve( + { _id: '1' }, + { first: 1 }, + { + dataSources: { + organization: { + connectionsByDomainId: jest.fn().mockReturnValue(expectedResult), + }, + }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(false), + }, + }, + ), + ).resolves.toEqual(expectedResult) + }) + }) + describe('testing the web resolver', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const response = await demoType.web.resolve( + { _id: '1' }, + { limit: 1 }, + { + dataSources: { + auth: { domainPermissionByDomainId: { load: jest.fn().mockResolvedValue(true) } }, + webScan: { getConnectionsByDomainId: jest.fn().mockReturnValue({ _id: '1', _key: '1' }) }, + }, + auth: { + userRequired: jest.fn().mockReturnValue(true), + }, + }, + ) + + expect(response).toEqual({ + _id: '1', + _key: '1', + }) + }) + }) + describe('testing the DNS resolver', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const response = await demoType.dnsScan.resolve( + { _id: '1' }, + { limit: 1 }, + { + dataSources: { + auth: { domainPermissionByDomainId: { load: jest.fn().mockResolvedValue(true) } }, + dnsScan: { getConnectionsByDomainId: jest.fn().mockReturnValue({ _id: '1', _key: '1' }) }, + }, + auth: { + userRequired: jest.fn().mockReturnValue(true), + }, + }, + ) + + expect(response).toEqual({ + _id: '1', + _key: '1', + }) + }) + }) + describe('testing the dmarcSummaryByPeriod resolver', () => { + let i18n + describe('user has domain ownership permission', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const data = { + _id: 'domains/1', + _key: '1', + domain: 'test1.gc.ca', + } + + await expect( + demoType.dmarcSummaryByPeriod.resolve( + data, + { + month: 'january', + year: '2021', + }, + { + userKey: '1', + loaders: { + loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn().mockReturnValue({ + _to: 'dmarcSummaries/1', + }), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), + }, + auth: { + checkDomainOwnership: jest.fn().mockReturnValue(true), + userRequired: jest.fn(), + tokenize, + }, + }, + ), + ).resolves.toEqual({ + _id: 'dmarcSummaries/1', + domainKey: '1', + startDate: '2021-01-01', + }) + }) + }) + describe('users language is english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user does not have domain ownership permission', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const data = { + _id: 'domains/1', + _key: '1', + domain: 'test1.gc.ca', + } + + await expect( + demoType.dmarcSummaryByPeriod.resolve( + data, + {}, + { + i18n, + userKey: '1', + loaders: { + loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), + }, + auth: { + checkDomainOwnership: jest.fn().mockReturnValue(false), + userRequired: jest.fn(), + loginRequiredBool: true, + }, + }, + ), + ).rejects.toEqual(new Error('Unable to retrieve DMARC report information for: test1.gc.ca')) + + expect(consoleOutput).toEqual([ + `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, + ]) + }) + }) + }) + describe('users language is french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user does not have domain ownership permission', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const data = { + _id: 'domains/1', + _key: '1', + domain: 'test1.gc.ca', + } + + await expect( + demoType.dmarcSummaryByPeriod.resolve( + data, + {}, + { + i18n, + userKey: '1', + loaders: { + loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), + }, + auth: { + checkDomainOwnership: jest.fn().mockReturnValue(false), + userRequired: jest.fn(), + loginRequiredBool: true, + }, + }, + ), + ).rejects.toEqual(new Error('Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca')) + + expect(consoleOutput).toEqual([ + `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, + ]) + }) + }) + }) + }) + describe('testing the yearlyDmarcSummaries resolver', () => { + let i18n + describe('user has domain ownership permission', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + const data = { + _id: 'domains/1', + _key: '1', + domain: 'test1.gc.ca', + } + + await expect( + demoType.yearlyDmarcSummaries.resolve( + data, + {}, + { + userKey: '1', + loaders: { + loadDmarcYearlySumEdge: jest.fn().mockReturnValue([ + { + domainKey: '1', + _to: 'dmarcSummaries/1', + startDate: '2021-01-01', + }, + ]), + }, + auth: { + checkDomainOwnership: jest.fn().mockReturnValue(true), + userRequired: jest.fn(), + }, + }, + ), + ).resolves.toEqual([ + { + _id: 'dmarcSummaries/1', + domainKey: '1', + startDate: '2021-01-01', + }, + ]) + }) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user does not have domain ownership permission', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const data = { + _id: 'domains/1', + _key: '1', + domain: 'test1.gc.ca', + } + + await expect( + demoType.yearlyDmarcSummaries.resolve( + data, + {}, + { + i18n, + request: { + language: 'fr', + }, + userKey: '1', + loaders: { + loadDmarcYearlySumEdge: jest.fn(), + }, + auth: { + checkDomainOwnership: jest.fn().mockReturnValue(false), + userRequired: jest.fn(), + loginRequiredBool: true, + }, + }, + ), + ).rejects.toEqual(new Error('Unable to retrieve DMARC report information for: test1.gc.ca')) + expect(consoleOutput).toEqual([ + `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, + ]) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user does not have domain ownership permission', () => { + it('returns the resolved value', async () => { + const demoType = domainType.getFields() + + const data = { + _id: 'domains/1', + _key: '1', + domain: 'test1.gc.ca', + } + + await expect( + demoType.yearlyDmarcSummaries.resolve( + data, + {}, + { + i18n, + userKey: '1', + loaders: { + loadDmarcYearlySumEdge: jest.fn(), + }, + auth: { + checkDomainOwnership: jest.fn().mockReturnValue(false), + userRequired: jest.fn(), + loginRequiredBool: true, + }, + }, + ), + ).rejects.toEqual(new Error('Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca')) + expect(consoleOutput).toEqual([ + `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, + ]) + }) + }) + }) + }) + }) +}) diff --git a/api/src/domain/objects/domain-bulk-result.js b/api/src/domain/objects/domain-bulk-result.js new file mode 100644 index 0000000000..618af9bb15 --- /dev/null +++ b/api/src/domain/objects/domain-bulk-result.js @@ -0,0 +1,14 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' + +export const domainBulkResultType = new GraphQLObjectType({ + name: 'DomainBulkResult', + description: + 'This object is used to inform the user that no errors were encountered while mutating a domain.', + fields: () => ({ + status: { + type: GraphQLString, + description: 'Informs the user if the domain mutation was successful.', + resolve: ({ status }) => status, + }, + }), +}) diff --git a/api/src/domain/objects/domain-connection.js b/api/src/domain/objects/domain-connection.js new file mode 100644 index 0000000000..851b5b32a7 --- /dev/null +++ b/api/src/domain/objects/domain-connection.js @@ -0,0 +1,16 @@ +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' + +import {domainType} from './domain' + +export const domainConnection = connectionDefinitions({ + name: 'Domain', + nodeType: domainType, + connectionFields: () => ({ + totalCount: { + type: GraphQLInt, + description: 'The total amount of domains the user has access to.', + resolve: ({totalCount}) => totalCount, + }, + }), +}) diff --git a/api-js/src/domain/objects/domain-error.js b/api/src/domain/objects/domain-error.js similarity index 75% rename from api-js/src/domain/objects/domain-error.js rename to api/src/domain/objects/domain-error.js index 4d625fa303..b4e66613e2 100644 --- a/api-js/src/domain/objects/domain-error.js +++ b/api/src/domain/objects/domain-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const domainErrorType = new GraphQLObjectType({ name: 'DomainError', @@ -8,12 +8,12 @@ export const domainErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/domain/objects/domain-result.js b/api/src/domain/objects/domain-result.js new file mode 100644 index 0000000000..9791e43cf3 --- /dev/null +++ b/api/src/domain/objects/domain-result.js @@ -0,0 +1,21 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' + +import {domainType} from './domain' + +export const domainResultType = new GraphQLObjectType({ + name: 'DomainResult', + description: + 'This object is used to inform the user that no errors were encountered while mutating a domain.', + fields: () => ({ + status: { + type: GraphQLString, + description: 'Informs the user if the domain mutation was successful.', + resolve: ({status}) => status, + }, + domain: { + type: domainType, + description: 'The domain that is being mutated.', + resolve: ({domain}) => domain, + }, + }), +}) diff --git a/api/src/domain/objects/domain-status.js b/api/src/domain/objects/domain-status.js new file mode 100644 index 0000000000..948efc31b8 --- /dev/null +++ b/api/src/domain/objects/domain-status.js @@ -0,0 +1,66 @@ +import { GraphQLObjectType } from 'graphql' +import { StatusEnum } from '../../enums' + +export const domainStatus = new GraphQLObjectType({ + name: 'DomainStatus', + description: + 'This object contains how the domain is doing on the various scans we preform, based on the latest scan data.', + fields: () => ({ + certificates: { + type: StatusEnum, + description: 'Certificates Status', + resolve: ({ certificates }) => certificates, + }, + ciphers: { + type: StatusEnum, + description: 'Ciphers Status', + resolve: ({ ciphers }) => ciphers, + }, + curves: { + type: StatusEnum, + description: 'Curves Status', + resolve: ({ curves }) => curves, + }, + dkim: { + type: StatusEnum, + description: 'DKIM Status', + resolve: ({ dkim }) => dkim, + }, + dmarc: { + type: StatusEnum, + description: 'DMARC Status', + resolve: ({ dmarc }) => dmarc, + }, + https: { + type: StatusEnum, + description: 'HTTPS Status', + resolve: ({ https }) => https, + }, + hsts: { + type: StatusEnum, + description: 'HSTS Status', + resolve: ({ hsts }) => hsts, + }, + policy: { + type: StatusEnum, + description: 'Policy Status', + resolve: ({ ciphers, https, hsts, protocols, ssl }) => + [ciphers, https, hsts, protocols, ssl].every((t) => t !== 'fail') ? 'pass' : 'fail', + }, + protocols: { + type: StatusEnum, + description: 'Protocols Status', + resolve: ({ protocols }) => protocols, + }, + spf: { + type: StatusEnum, + description: 'SPF Status', + resolve: ({ spf }) => spf, + }, + ssl: { + type: StatusEnum, + description: 'SSL Status', + resolve: ({ ssl }) => ssl, + }, + }), +}) diff --git a/api/src/domain/objects/domain.js b/api/src/domain/objects/domain.js new file mode 100644 index 0000000000..21956c7543 --- /dev/null +++ b/api/src/domain/objects/domain.js @@ -0,0 +1,381 @@ +import { t } from '@lingui/macro' +import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql' +import { connectionArgs, globalIdField } from 'graphql-relay' + +import { domainStatus } from './domain-status' +import { AssetStateEnums, PeriodEnums, DmarcPhaseEnum } from '../../enums' +import { nodeInterface } from '../../node' +import { CveID, Domain, Selectors, Year } from '../../scalars' +import { dmarcSummaryType } from '../../dmarc-summaries/objects' +import { dnsScanConnection } from '../../dns-scan/objects/dns-scan-connection' +import { webConnection } from '../../web-scan/objects' +import { organizationOrder } from '../../organization/inputs' +import { organizationConnection } from '../../organization/objects' +import { GraphQLDateTime } from 'graphql-scalars' +import { dnsOrder } from '../../dns-scan/inputs' +import { webOrder } from '../../web-scan/inputs/web-order' +import { additionalFinding } from '../../additional-findings/objects/additional-finding' +import { tagType } from '../../tags/objects' +import { cvdEnrollment } from '../../additional-findings/objects' + +export const domainType = new GraphQLObjectType({ + name: 'Domain', + fields: () => ({ + id: globalIdField('domain'), + domain: { + type: Domain, + description: 'Domain that scans will be ran on.', + resolve: ({ domain }) => domain, + }, + dmarcPhase: { + type: DmarcPhaseEnum, + description: 'The current dmarc phase the domain is compliant to.', + resolve: ({ phase }) => phase, + }, + hasDMARCReport: { + type: GraphQLBoolean, + description: 'Whether or not the domain has a aggregate dmarc report.', + resolve: async ({ _id }, _, { auth: { checkDomainOwnership } }) => { + return await checkDomainOwnership({ + domainId: _id, + }) + }, + }, + lastRan: { + type: GraphQLString, + description: 'The last time that a scan was ran on this domain.', + resolve: ({ lastRan }) => lastRan, + }, + rcode: { + type: GraphQLString, + description: `The status code when performing a DNS lookup for this domain.`, + }, + selectors: { + type: new GraphQLList(Selectors), + description: 'Domain Keys Identified Mail (DKIM) selector strings associated with domain.', + resolve: async ( + { _id }, + _, + { userKey, auth: { userRequired }, dataSources: { auth: authDS }, loaders: { loadDkimSelectorsByDomainId } }, + ) => { + await userRequired() + const permitted = await authDS.domainPermissionByDomainId.load(_id) + if (!permitted) { + console.warn(`User: ${userKey} attempted to access selectors for ${_id}, but does not have permission.`) + throw new Error(t`Cannot query domain selectors without permission.`) + } + + return await loadDkimSelectorsByDomainId({ + domainId: _id, + }) + }, + }, + status: { + type: domainStatus, + description: 'The domains scan status, based on the latest scan data.', + resolve: ({ status }) => status, + }, + archived: { + description: 'Value that determines if a domain is excluded from any results and scans.', + type: GraphQLBoolean, + resolve: ({ archived }) => archived, + }, + blocked: { + description: 'Value that determines if a domain is possibly blocked.', + type: GraphQLBoolean, + resolve: ({ blocked }) => blocked, + }, + wildcardSibling: { + description: 'Value that determines if a domain has a wildcard sibling.', + type: GraphQLBoolean, + resolve: ({ wildcardSibling }) => wildcardSibling, + }, + wildcardEntry: { + description: 'Value that determines if a domain has a wildcard entry.', + type: GraphQLBoolean, + resolve: ({ wildcardEntry }) => wildcardEntry, + }, + webScanPending: { + description: 'Value that determines if a domain has a web scan pending.', + type: GraphQLBoolean, + resolve: ({ webScanPending }) => webScanPending, + }, + organizations: { + type: organizationConnection.connectionType, + args: { + orderBy: { + type: organizationOrder, + description: 'Ordering options for organization connections', + }, + search: { + type: GraphQLString, + description: 'String argument used to search for organizations.', + }, + isAdmin: { + type: GraphQLBoolean, + description: 'Filter orgs based off of the user being an admin of them.', + }, + includeSuperAdminOrg: { + type: GraphQLBoolean, + description: 'Filter org list to either include or exclude the super admin org.', + }, + ...connectionArgs, + }, + description: 'The organization that this domain belongs to.', + resolve: async ({ _id }, args, { auth: { checkSuperAdmin }, dataSources: { organization } }) => { + const isSuperAdmin = await checkSuperAdmin() + + return await organization.connectionsByDomainId({ + domainId: _id, + isSuperAdmin, + ...args, + }) + }, + }, + dnsScan: { + type: dnsScanConnection.connectionType, + args: { + startDate: { + type: GraphQLDateTime, + description: 'Start date for date filter.', + }, + endDate: { + type: GraphQLDateTime, + description: 'End date for date filter.', + }, + orderBy: { + type: dnsOrder, + description: 'Ordering options for DNS connections.', + }, + limit: { + type: GraphQLInt, + description: 'Number of DNS scans to retrieve.', + }, + ...connectionArgs, + }, + description: `DNS scan results.`, + resolve: async ({ _id }, args, { userKey, auth: { userRequired }, dataSources: { auth: authDS, dnsScan } }) => { + await userRequired() + const permitted = await authDS.domainPermissionByDomainId.load(_id) + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access dns scan results for ${_id}, but does not have permission.`, + ) + throw new Error(t`Cannot query dns scan results without permission.`) + } + + return await dnsScan.getConnectionsByDomainId({ + domainId: _id, + ...args, + }) + }, + }, + web: { + type: webConnection.connectionType, + description: 'HTTPS, and TLS scan results.', + args: { + startDate: { + type: GraphQLDateTime, + description: 'Start date for date filter.', + }, + endDate: { + type: GraphQLDateTime, + description: 'End date for date filter.', + }, + orderBy: { + type: webOrder, + description: 'Ordering options for web connections.', + }, + limit: { + type: GraphQLInt, + description: 'Number of web scans to retrieve.', + }, + excludePending: { + type: GraphQLBoolean, + description: `Exclude web scans which have pending status.`, + }, + ...connectionArgs, + }, + resolve: async ({ _id }, args, { userKey, auth: { userRequired }, dataSources: { auth: authDS, webScan } }) => { + await userRequired() + const permitted = await authDS.domainPermissionByDomainId.load(_id) + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access web scan results for ${_id}, but does not have permission.`, + ) + throw new Error(t`Cannot query web scan results without permission.`) + } + + return await webScan.getConnectionsByDomainId({ + domainId: _id, + ...args, + }) + }, + }, + additionalFindings: { + type: additionalFinding, + description: 'Additional findings imported from an external ASM tool.', + resolve: async ( + { _id }, + _, + { userKey, auth: { userRequired }, dataSources: { auth: authDS, additionalFindings } }, + ) => { + await userRequired() + const permitted = await authDS.domainPermissionByDomainId.load(_id) + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access additional findings for domain: ${_id}, but does not have permission.`, + ) + throw new Error(t`Cannot query additional findings without permission.`) + } + + return await additionalFindings.getByDomainId({ + domainId: _id, + }) + }, + }, + ignoredCves: { + type: new GraphQLList(CveID), + description: 'List of CVEs that have been ignored by the user.', + resolve: ({ ignoredCves }) => ignoredCves || [], + }, + dmarcSummaryByPeriod: { + description: 'Summarized DMARC aggregate reports.', + args: { + month: { + type: new GraphQLNonNull(PeriodEnums), + description: 'The month in which the returned data is relevant to.', + }, + year: { + type: new GraphQLNonNull(Year), + description: 'The year in which the returned data is relevant to.', + }, + }, + type: dmarcSummaryType, + resolve: async ( + { _id, _key, domain }, + { month, year }, + { + i18n, + userKey, + loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod, loadStartDateFromPeriod }, + auth: { checkDomainOwnership, userRequired }, + }, + ) => { + await userRequired() + const permitted = await checkDomainOwnership({ + domainId: _id, + }) + + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, + ) + throw new Error(i18n._(t`Unable to retrieve DMARC report information for: ${domain}`)) + } + + const startDate = loadStartDateFromPeriod({ period: month, year }) + + const dmarcSummaryEdge = await loadDmarcSummaryEdgeByDomainIdAndPeriod({ + domainId: _id, + startDate, + }) + + return { + domainKey: _key, + _id: dmarcSummaryEdge._to, + startDate, + } + }, + }, + yearlyDmarcSummaries: { + description: 'Yearly summarized DMARC aggregate reports.', + type: new GraphQLList(dmarcSummaryType), + resolve: async ( + { _id, _key, domain }, + __, + { i18n, userKey, loaders: { loadDmarcYearlySumEdge }, auth: { checkDomainOwnership, userRequired } }, + ) => { + await userRequired() + + const permitted = await checkDomainOwnership({ + domainId: _id, + }) + + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, + ) + throw new Error(i18n._(t`Unable to retrieve DMARC report information for: ${domain}`)) + } + + const dmarcSummaryEdges = await loadDmarcYearlySumEdge({ + domainId: _id, + }) + + return dmarcSummaryEdges.map((edge) => ({ + domainKey: _key, + _id: edge._to, + startDate: edge.startDate, + })) + }, + }, + claimTags: { + description: 'List of labelled tags users of an organization have applied to the claimed domain.', + type: new GraphQLList(tagType), + args: { + isVisible: { + type: GraphQLBoolean, + description: 'Filter tags to only those that are visible.', + defaultValue: true, + }, + }, + resolve: async ({ claimTags }, args, { loaders: { loadTagByTagId } }) => { + const loadedTags = await loadTagByTagId.loadMany(claimTags) + return loadedTags.filter((tag) => { + return args.isVisible ? tag.visible : true + }) + }, + }, + userHasPermission: { + description: + 'Value that determines if a user is affiliated with a domain, whether through organization affiliation, verified organization network affiliation, or through super admin status.', + type: GraphQLBoolean, + resolve: async ({ _id }, __, { dataSources: { auth: authDS } }) => { + return await authDS.domainPermissionByDomainId.load(_id) + }, + }, + ignoreRua: { + description: 'Value that determines if a domain is ignoring rua reports.', + type: GraphQLBoolean, + resolve: ({ ignoreRua }) => ignoreRua, + }, + assetState: { + description: 'Value that determines if a domain is considered an asset.', + type: AssetStateEnums, + resolve: ({ assetState }) => assetState, + }, + hasEntrustCertificate: { + type: GraphQLBoolean, + description: `Whether or not the certificate chain contains an Entrust certificate.`, + resolve: ({ hasEntrustCertificate }) => hasEntrustCertificate, + }, + cveDetected: { + type: GraphQLBoolean, + description: `Whether or not a CVE has been detected in the domain's additional findings.`, + resolve: ({ cveDetected }) => cveDetected, + }, + cvdEnrollment: { + type: cvdEnrollment, + description: + 'The Coordinated Vulnerability Disclosure (CVD) enrollment status and requirements for this domain asset, including HackerOne integration details.', + resolve: async ({ cvdEnrollment }, __, { auth: { userRequired } }) => { + await userRequired() + + return cvdEnrollment + }, + }, + }), + interfaces: [nodeInterface], + description: 'Domain object containing information for a given domain.', +}) diff --git a/api/src/domain/objects/index.js b/api/src/domain/objects/index.js new file mode 100644 index 0000000000..a5d96fad94 --- /dev/null +++ b/api/src/domain/objects/index.js @@ -0,0 +1,6 @@ +export * from './domain' +export * from './domain-connection' +export * from './domain-error' +export * from './domain-result' +export * from './domain-status' +export * from './domain-bulk-result' diff --git a/api-js/src/domain/queries/__tests__/find-domain-by-domain.test.js b/api/src/domain/queries/__tests__/find-domain-by-domain.test.js similarity index 81% rename from api-js/src/domain/queries/__tests__/find-domain-by-domain.test.js rename to api/src/domain/queries/__tests__/find-domain-by-domain.test.js index b752c3169b..dea6da436b 100644 --- a/api-js/src/domain/queries/__tests__/find-domain-by-domain.test.js +++ b/api/src/domain/queries/__tests__/find-domain-by-domain.test.js @@ -1,21 +1,18 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' -import { - checkDomainPermission, - userRequired, - verifiedRequired, -} from '../../../auth' -import { loadDomainByDomain } from '../../loaders' +import { checkDomainPermission, userRequired, verifiedRequired, AuthDataSource } from '../../../auth' +import { loadDkimSelectorsByDomainId, loadDomainByDomain } from '../../loaders' import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -43,11 +40,15 @@ describe('given findDomainByDomain query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -82,7 +83,6 @@ describe('given findDomainByDomain query', () => { domain = await collections.domains.save({ domain: 'test.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], status: { dkim: 'pass', dmarc: 'pass', @@ -91,6 +91,16 @@ describe('given findDomainByDomain query', () => { ssl: 'fail', }, }) + const selector1 = await collections.selectors.save({ selector: 'selector1' }) + const selector2 = await collections.selectors.save({ selector: 'selector2' }) + await collections.domainsToSelectors.save({ + _from: domain._id, + _to: selector1._id, + }) + await collections.domainsToSelectors.save({ + _from: domain._id, + _to: selector2._id, + }) await collections.claims.save({ _to: domain._id, _from: org._id, @@ -109,9 +119,9 @@ describe('given findDomainByDomain query', () => { }) describe('authorized user queries domain by domain', () => { it('returns domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findDomainByDomain(domain: "test.gc.ca") { id @@ -128,12 +138,13 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, - query: query, + query, auth: { + loginRequiredBool: true, checkDomainPermission: checkDomainPermission({ query, userKey: user._key, @@ -144,15 +155,25 @@ describe('given findDomainByDomain query', () => { }), verifiedRequired: verifiedRequired({}), }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, validators: { cleanseInput, }, loaders: { loadDomainByDomain: loadDomainByDomain({ query }), loadUserByKey: loadUserByKey({ query }), + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -160,7 +181,7 @@ describe('given findDomainByDomain query', () => { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'PASS', dmarc: 'PASS', @@ -172,9 +193,7 @@ describe('given findDomainByDomain query', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved domain ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved domain ${domain._key}.`]) }) }) }) @@ -197,9 +216,9 @@ describe('given findDomainByDomain query', () => { describe('given unsuccessful domain retrieval', () => { describe('domain cannot be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findDomainByDomain(domain: "not-test.gc.ca") { id @@ -216,11 +235,11 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 1, - query: query, + query, auth: { checkDomainPermission: jest.fn().mockReturnValue(true), userRequired: jest.fn().mockReturnValue({ @@ -237,11 +256,9 @@ describe('given findDomainByDomain query', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError(`Unable to find the requested domain.`), - ] + const error = [new GraphQLError(`Unable to find the requested domain.`)] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([`User 1 could not retrieve domain.`]) @@ -249,9 +266,9 @@ describe('given findDomainByDomain query', () => { }) describe('user does not belong to an org which claims domain', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findDomainByDomain(domain: "not-test.gc.ca") { id @@ -268,8 +285,8 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: '1', query: jest.fn(), @@ -279,6 +296,7 @@ describe('given findDomainByDomain query', () => { _key: '1', }), verifiedRequired: jest.fn(), + loginRequiredBool: true, }, validators: { cleanseInput, @@ -289,7 +307,7 @@ describe('given findDomainByDomain query', () => { }, }, }, - ) + }) const error = [ new GraphQLError( @@ -320,9 +338,9 @@ describe('given findDomainByDomain query', () => { }) describe('domain cannot be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findDomainByDomain(domain: "not-test.gc.ca") { id @@ -332,11 +350,11 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 1, - query: query, + query, auth: { checkDomainPermission: jest.fn().mockReturnValue(true), userRequired: jest.fn().mockReturnValue({ @@ -353,11 +371,9 @@ describe('given findDomainByDomain query', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Impossible de trouver le domaine demandé.'), - ] + const error = [new GraphQLError('Impossible de trouver le domaine demandé.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([`User 1 could not retrieve domain.`]) @@ -365,9 +381,9 @@ describe('given findDomainByDomain query', () => { }) describe('user does not belong to an org which claims domain', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findDomainByDomain(domain: "not-test.gc.ca") { id @@ -384,8 +400,8 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: '1', query: jest.fn(), @@ -395,6 +411,7 @@ describe('given findDomainByDomain query', () => { _key: '1', }), verifiedRequired: jest.fn(), + loginRequiredBool: true, }, validators: { cleanseInput, @@ -405,7 +422,7 @@ describe('given findDomainByDomain query', () => { }, }, }, - ) + }) const error = [ new GraphQLError( diff --git a/api-js/src/domain/queries/__tests__/find-my-domains.test.js b/api/src/domain/queries/__tests__/find-my-domains.test.js similarity index 76% rename from api-js/src/domain/queries/__tests__/find-my-domains.test.js rename to api/src/domain/queries/__tests__/find-my-domains.test.js index 9d8ea1201a..db218facd9 100644 --- a/api-js/src/domain/queries/__tests__/find-my-domains.test.js +++ b/api/src/domain/queries/__tests__/find-my-domains.test.js @@ -1,17 +1,18 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' -import { checkSuperAdmin, userRequired, verifiedRequired } from '../../../auth' -import { loadDomainConnectionsByUserId } from '../../loaders' +import { AuthDataSource, checkDomainPermission, checkSuperAdmin, userRequired, verifiedRequired } from '../../../auth' +import { loadDkimSelectorsByDomainId, loadDomainConnectionsByUserId } from '../../loaders' import { loadUserByKey } from '../../../user' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -40,18 +41,21 @@ describe('given findMyDomainsQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'french', emailValidated: true, }) @@ -87,7 +91,6 @@ describe('given findMyDomainsQuery', () => { domainOne = await collections.domains.save({ domain: 'test1.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], status: { dkim: 'pass', dmarc: 'pass', @@ -99,7 +102,6 @@ describe('given findMyDomainsQuery', () => { domainTwo = await collections.domains.save({ domain: 'test2.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], status: { dkim: 'pass', dmarc: 'pass', @@ -108,6 +110,24 @@ describe('given findMyDomainsQuery', () => { ssl: 'fail', }, }) + const selector1 = await collections.selectors.save({ selector: 'selector1' }) + const selector2 = await collections.selectors.save({ selector: 'selector2' }) + await collections.domainsToSelectors.save({ + _from: domainOne._id, + _to: selector1._id, + }) + await collections.domainsToSelectors.save({ + _from: domainTwo._id, + _to: selector1._id, + }) + await collections.domainsToSelectors.save({ + _from: domainOne._id, + _to: selector2._id, + }) + await collections.domainsToSelectors.save({ + _from: domainTwo._id, + _to: selector2._id, + }) await collections.claims.save({ _to: domainOne._id, _from: org._id, @@ -125,9 +145,9 @@ describe('given findMyDomainsQuery', () => { }) describe('user queries for their domains', () => { it('returns domains', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDomains(first: 5) { edges { @@ -149,11 +169,16 @@ describe('given findMyDomainsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkSuperAdmin: checkSuperAdmin({ i18n, userKey: user._key, @@ -170,15 +195,26 @@ describe('given findMyDomainsQuery', () => { }), verifiedRequired: verifiedRequired({}), }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, loaders: { loadDomainConnectionsByUserId: loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, + }), + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, }), }, }, - ) + }) const expectedResponse = { data: { @@ -190,7 +226,7 @@ describe('given findMyDomainsQuery', () => { id: toGlobalId('domain', domainOne._key), domain: 'test1.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], }, }, { @@ -199,7 +235,7 @@ describe('given findMyDomainsQuery', () => { id: toGlobalId('domain', domainTwo._key), domain: 'test2.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], }, }, ], @@ -214,9 +250,7 @@ describe('given findMyDomainsQuery', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully retrieved their domains.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully retrieved their domains.`]) }) }) }) @@ -238,13 +272,11 @@ describe('given findMyDomainsQuery', () => { describe('given an error thrown during retrieving domains', () => { describe('user queries for their domains', () => { it('returns domains', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDomains(first: 5) { edges { @@ -266,8 +298,8 @@ describe('given findMyDomainsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 1, auth: { @@ -280,15 +312,14 @@ describe('given findMyDomainsQuery', () => { query: mockedQuery, userKey: 1, cleanseInput, + auth: { loginRequired: true }, i18n, }), }, }, - ) + }) - const error = [ - new GraphQLError(`Unable to query domain(s). Please try again.`), - ] + const error = [new GraphQLError(`Unable to query domain(s). Please try again.`)] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -316,13 +347,11 @@ describe('given findMyDomainsQuery', () => { describe('given an error thrown during retrieving domains', () => { describe('user queries for their domains', () => { it('returns domains', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyDomains(first: 5) { edges { @@ -344,8 +373,8 @@ describe('given findMyDomainsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 1, auth: { @@ -358,17 +387,14 @@ describe('given findMyDomainsQuery', () => { query: mockedQuery, userKey: 1, cleanseInput, + auth: { loginRequired: true }, i18n, }), }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible d'interroger le(s) domaine(s). Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible d'interroger le(s) domaine(s). Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/domain/queries/find-domain-by-domain.js b/api/src/domain/queries/find-domain-by-domain.js new file mode 100644 index 0000000000..ee896ae55e --- /dev/null +++ b/api/src/domain/queries/find-domain-by-domain.js @@ -0,0 +1,60 @@ +import { GraphQLNonNull } from 'graphql' +import { t } from '@lingui/macro' +import { Domain } from '../../scalars' + +import { domainType } from '../objects' + +export const findDomainByDomain = { + type: domainType, + description: 'Retrieve a specific domain by providing a domain.', + args: { + domain: { + type: new GraphQLNonNull(Domain), + description: 'The domain you wish to retrieve information for.', + }, + }, + resolve: async ( + _, + args, + { + i18n, + userKey, + auth: { checkDomainPermission, userRequired, verifiedRequired, loginRequiredBool }, + loaders: { loadDomainByDomain }, + validators: { cleanseInput }, + }, + ) => { + if (loginRequiredBool) { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + } + + // Cleanse input + const domainInput = cleanseInput(args.domain) + + // Retrieve domain by domain + const domain = await loadDomainByDomain.load(domainInput) + + if (typeof domain === 'undefined') { + console.warn(`User ${userKey} could not retrieve domain.`) + throw new Error(i18n._(t`Unable to find the requested domain.`)) + } + + if (loginRequiredBool) { + // Check user permission for domain access + const permitted = await checkDomainPermission({ domainId: domain._id }) + + if (!permitted) { + console.warn(`User ${userKey} could not retrieve domain.`) + throw new Error( + i18n._(t`Permission Denied: Please contact organization user for help with retrieving this domain.`), + ) + } + } + + console.info(`User ${userKey} successfully retrieved domain ${domain._key}.`) + + return domain + }, +} diff --git a/api/src/domain/queries/find-my-domains.js b/api/src/domain/queries/find-my-domains.js new file mode 100644 index 0000000000..870013d1de --- /dev/null +++ b/api/src/domain/queries/find-my-domains.js @@ -0,0 +1,58 @@ +import { GraphQLBoolean, GraphQLString, GraphQLList } from 'graphql' +import { connectionArgs } from 'graphql-relay' + +import { domainFilter, domainOrder } from '../inputs' +import { domainConnection } from '../objects' + +export const findMyDomains = { + type: domainConnection.connectionType, + description: 'Select domains a user has access to.', + args: { + orderBy: { + type: domainOrder, + description: 'Ordering options for domain connections.', + }, + ownership: { + type: GraphQLBoolean, + description: 'Limit domains to those that belong to an organization that has ownership.', + }, + search: { + type: GraphQLString, + description: 'String used to search for domains.', + }, + isAffiliated: { + type: GraphQLBoolean, + description: 'Filter the results based on the users affiliation.', + }, + filters: { + type: new GraphQLList(domainFilter), + description: 'Filters used to limit domains returned.', + }, + ...connectionArgs, + }, + resolve: async ( + _, + args, + { + userKey, + auth: { checkSuperAdmin, userRequired, loginRequiredBool, verifiedRequired }, + loaders: { loadDomainConnectionsByUserId }, + }, + ) => { + if (loginRequiredBool) { + const user = await userRequired() + verifiedRequired({ user }) + } + + const isSuperAdmin = await checkSuperAdmin() + + const domainConnections = await loadDomainConnectionsByUserId({ + isSuperAdmin, + ...args, + }) + + console.info(`User: ${userKey} successfully retrieved their domains.`) + + return domainConnections + }, +} diff --git a/api-js/src/domain/queries/index.js b/api/src/domain/queries/index.js similarity index 100% rename from api-js/src/domain/queries/index.js rename to api/src/domain/queries/index.js diff --git a/api-js/src/domain/unions/__tests__/create-domain-union.test.js b/api/src/domain/unions/__tests__/create-domain-union.test.js similarity index 85% rename from api-js/src/domain/unions/__tests__/create-domain-union.test.js rename to api/src/domain/unions/__tests__/create-domain-union.test.js index 32d2a49e94..11edf2c451 100644 --- a/api-js/src/domain/unions/__tests__/create-domain-union.test.js +++ b/api/src/domain/unions/__tests__/create-domain-union.test.js @@ -22,7 +22,7 @@ describe('given the createDomainUnion', () => { domain: {}, } - expect(createDomainUnion.resolveType(obj)).toMatchObject(domainType) + expect(createDomainUnion.resolveType(obj)).toMatch(domainType.name) }) }) describe('testing the domainErrorType', () => { @@ -34,9 +34,7 @@ describe('given the createDomainUnion', () => { description: 'text', } - expect(createDomainUnion.resolveType(obj)).toMatchObject( - domainErrorType, - ) + expect(createDomainUnion.resolveType(obj)).toMatch(domainErrorType.name) }) }) }) diff --git a/api-js/src/domain/unions/__tests__/remove-domain-union.test.js b/api/src/domain/unions/__tests__/remove-domain-union.test.js similarity index 83% rename from api-js/src/domain/unions/__tests__/remove-domain-union.test.js rename to api/src/domain/unions/__tests__/remove-domain-union.test.js index ba55a11f40..f3ae3326a4 100644 --- a/api-js/src/domain/unions/__tests__/remove-domain-union.test.js +++ b/api/src/domain/unions/__tests__/remove-domain-union.test.js @@ -22,9 +22,7 @@ describe('given the removeDomainUnion', () => { status: 'status', } - expect(removeDomainUnion.resolveType(obj)).toMatchObject( - domainResultType, - ) + expect(removeDomainUnion.resolveType(obj)).toMatch(domainResultType.name) }) }) describe('testing the domainErrorType', () => { @@ -36,9 +34,7 @@ describe('given the removeDomainUnion', () => { description: 'text', } - expect(removeDomainUnion.resolveType(obj)).toMatchObject( - domainErrorType, - ) + expect(removeDomainUnion.resolveType(obj)).toMatch(domainErrorType.name) }) }) }) diff --git a/api-js/src/domain/unions/__tests__/update-domain-union.test.js b/api/src/domain/unions/__tests__/update-domain-union.test.js similarity index 85% rename from api-js/src/domain/unions/__tests__/update-domain-union.test.js rename to api/src/domain/unions/__tests__/update-domain-union.test.js index 97ec8b3fe3..22d73751c1 100644 --- a/api-js/src/domain/unions/__tests__/update-domain-union.test.js +++ b/api/src/domain/unions/__tests__/update-domain-union.test.js @@ -22,7 +22,7 @@ describe('given the updateDomainUnion', () => { domain: {}, } - expect(updateDomainUnion.resolveType(obj)).toMatchObject(domainType) + expect(updateDomainUnion.resolveType(obj)).toMatch(domainType.name) }) }) describe('testing the domainErrorType', () => { @@ -34,9 +34,7 @@ describe('given the updateDomainUnion', () => { description: 'text', } - expect(updateDomainUnion.resolveType(obj)).toMatchObject( - domainErrorType, - ) + expect(updateDomainUnion.resolveType(obj)).toMatch(domainErrorType.name) }) }) }) diff --git a/api/src/domain/unions/bulk-modify-domains-union.js b/api/src/domain/unions/bulk-modify-domains-union.js new file mode 100644 index 0000000000..e1538d2fb0 --- /dev/null +++ b/api/src/domain/unions/bulk-modify-domains-union.js @@ -0,0 +1,17 @@ +import { GraphQLUnionType } from 'graphql' +import { domainErrorType, domainBulkResultType } from '../objects' + +export const bulkModifyDomainsUnion = new GraphQLUnionType({ + name: 'BulkModifyDomainsUnion', + description: `This union is used with the \`AddOrganizationsDomains\` and \`RemoveOrganizationsDomains\` mutation, + allowing for users to add/remove multiple domains belonging to their org, + and support any errors that may occur`, + types: [domainErrorType, domainBulkResultType], + resolveType({ _type }) { + if (_type === 'result') { + return domainBulkResultType.name + } else { + return domainErrorType.name + } + }, +}) diff --git a/api-js/src/domain/unions/create-domain-union.js b/api/src/domain/unions/create-domain-union.js similarity index 75% rename from api-js/src/domain/unions/create-domain-union.js rename to api/src/domain/unions/create-domain-union.js index 72147aaf40..f19fdbfbc9 100644 --- a/api-js/src/domain/unions/create-domain-union.js +++ b/api/src/domain/unions/create-domain-union.js @@ -3,15 +3,15 @@ import { domainErrorType, domainType } from '../objects' export const createDomainUnion = new GraphQLUnionType({ name: 'CreateDomainUnion', - description: `This union is used with the \`CreateDomain\` mutation, -allowing for users to create a domain and add it to their org, + description: `This union is used with the \`CreateDomain\` mutation, +allowing for users to create a domain and add it to their org, and support any errors that may occur`, types: [domainErrorType, domainType], resolveType({ _type }) { if (_type === 'domain') { - return domainType + return domainType.name } else { - return domainErrorType + return domainErrorType.name } }, }) diff --git a/api/src/domain/unions/ignore-cve-union.js b/api/src/domain/unions/ignore-cve-union.js new file mode 100644 index 0000000000..aa3fba44fa --- /dev/null +++ b/api/src/domain/unions/ignore-cve-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { domainErrorType, domainType } from '../objects' + +export const ignoreCveUnion = new GraphQLUnionType({ + name: 'IgnoreCveUnion', + description: `This union is used with the \`IgnoreCve\` mutation, +allowing for users to ignore a CVE for a domain and support any errors that may occur`, + types: [domainErrorType, domainType], + resolveType({ _type }) { + if (_type === 'domain') { + return domainType.name + } else { + return domainErrorType.name + } + }, +}) diff --git a/api/src/domain/unions/index.js b/api/src/domain/unions/index.js new file mode 100644 index 0000000000..d5400b7efc --- /dev/null +++ b/api/src/domain/unions/index.js @@ -0,0 +1,4 @@ +export * from './bulk-modify-domains-union' +export * from './create-domain-union' +export * from './remove-domain-union' +export * from './update-domain-union' diff --git a/api-js/src/domain/unions/remove-domain-union.js b/api/src/domain/unions/remove-domain-union.js similarity index 75% rename from api-js/src/domain/unions/remove-domain-union.js rename to api/src/domain/unions/remove-domain-union.js index ca914fd234..1971fd6f56 100644 --- a/api-js/src/domain/unions/remove-domain-union.js +++ b/api/src/domain/unions/remove-domain-union.js @@ -3,15 +3,15 @@ import { domainErrorType, domainResultType } from '../objects' export const removeDomainUnion = new GraphQLUnionType({ name: 'RemoveDomainUnion', - description: `This union is used with the \`RemoveDomain\` mutation, -allowing for users to remove a domain belonging to their org, + description: `This union is used with the \`RemoveDomain\` mutation, +allowing for users to remove a domain belonging to their org, and support any errors that may occur`, types: [domainErrorType, domainResultType], resolveType({ _type }) { if (_type === 'result') { - return domainResultType + return domainResultType.name } else { - return domainErrorType + return domainErrorType.name } }, }) diff --git a/api-js/src/domain/unions/update-domain-union.js b/api/src/domain/unions/update-domain-union.js similarity index 75% rename from api-js/src/domain/unions/update-domain-union.js rename to api/src/domain/unions/update-domain-union.js index 3843656416..8a8b9a274e 100644 --- a/api-js/src/domain/unions/update-domain-union.js +++ b/api/src/domain/unions/update-domain-union.js @@ -3,15 +3,15 @@ import { domainErrorType, domainType } from '../objects' export const updateDomainUnion = new GraphQLUnionType({ name: 'UpdateDomainUnion', - description: `This union is used with the \`UpdateDomain\` mutation, -allowing for users to update a domain belonging to their org, + description: `This union is used with the \`UpdateDomain\` mutation, +allowing for users to update a domain belonging to their org, and support any errors that may occur`, types: [domainErrorType, domainType], resolveType({ _type }) { if (_type === 'domain') { - return domainType + return domainType.name } else { - return domainErrorType + return domainErrorType.name } }, }) diff --git a/api-js/src/enums/affiliation-org-order-field.js b/api/src/enums/affiliation-org-order-field.js similarity index 98% rename from api-js/src/enums/affiliation-org-order-field.js rename to api/src/enums/affiliation-org-order-field.js index b20c837099..8e362b59c3 100644 --- a/api-js/src/enums/affiliation-org-order-field.js +++ b/api/src/enums/affiliation-org-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const AffiliationOrgOrderField = new GraphQLEnumType({ name: 'AffiliationOrgOrderField', diff --git a/api/src/enums/affiliation-user-order-field.js b/api/src/enums/affiliation-user-order-field.js new file mode 100644 index 0000000000..c7cc3a41fe --- /dev/null +++ b/api/src/enums/affiliation-user-order-field.js @@ -0,0 +1,20 @@ +import { GraphQLEnumType } from 'graphql' + +export const AffiliationUserOrderField = new GraphQLEnumType({ + name: 'AffiliationUserOrderField', + description: 'Properties by which affiliation connections can be ordered.', + values: { + USERNAME: { + value: 'username', + description: 'Order affiliations by username.', + }, + DISPLAY_NAME: { + value: 'display_name', + description: 'Order affiliations by display name.', + }, + PERMISSION: { + value: 'permission', + description: 'Order affiliations by permission.', + }, + }, +}) diff --git a/api/src/enums/asset-state.js b/api/src/enums/asset-state.js new file mode 100644 index 0000000000..6fa8921c93 --- /dev/null +++ b/api/src/enums/asset-state.js @@ -0,0 +1,28 @@ +import { GraphQLEnumType } from 'graphql' + +export const AssetStateEnums = new GraphQLEnumType({ + name: 'AssetStateEnums', + values: { + APPROVED: { + value: 'approved', + description: 'An asset confirmed to belong to the organization.', + }, + DEPENDENCY: { + value: 'dependency', + description: 'An asset that is owned by a third party and supports the operation of organization-owned assets.', + }, + MONITOR_ONLY: { + value: 'monitor-only', + description: 'An asset that is relevant to the organization but is not a direct part of the attack surface.', + }, + CANDIDATE: { + value: 'candidate', + description: 'An asset that is suspected to belong to the organization but has not been confirmed.', + }, + REQUIRES_INVESTIGATION: { + value: 'requires-investigation', + description: 'An asset that requires further investigation to determine its relationship to the organization.', + }, + }, + description: "An enum used to describe how an asset relates to an organization's attack surface.", +}) diff --git a/api/src/enums/cvd-requirement.js b/api/src/enums/cvd-requirement.js new file mode 100644 index 0000000000..d37aa1b9c0 --- /dev/null +++ b/api/src/enums/cvd-requirement.js @@ -0,0 +1,20 @@ +import { GraphQLEnumType } from 'graphql' + +export const CvdRequirementEnums = new GraphQLEnumType({ + name: 'CvdRequirementEnums', + values: { + NONE: { + value: 'none', + description: 'No additional CVSS environmental requirement for this asset.', + }, + LOW: { + value: 'low', + description: 'Low CVSS environmental requirement for this asset.', + }, + HIGH: { + value: 'high', + description: 'High CVSS environmental requirement for this asset.', + }, + }, + description: 'Enumerates the CVSS environmental requirement levels for CVD-enrolled assets.', +}) diff --git a/api-js/src/enums/dkim-order-field.js b/api/src/enums/dkim-order-field.js similarity index 86% rename from api-js/src/enums/dkim-order-field.js rename to api/src/enums/dkim-order-field.js index 7392508922..ed8565ec6e 100644 --- a/api-js/src/enums/dkim-order-field.js +++ b/api/src/enums/dkim-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const DkimOrderField = new GraphQLEnumType({ name: 'DKIMOrderField', diff --git a/api-js/src/enums/dkim-result-order-field.js b/api/src/enums/dkim-result-order-field.js similarity index 92% rename from api-js/src/enums/dkim-result-order-field.js rename to api/src/enums/dkim-result-order-field.js index b79cd5bf84..e24c2f4208 100644 --- a/api-js/src/enums/dkim-result-order-field.js +++ b/api/src/enums/dkim-result-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const DkimResultOrderField = new GraphQLEnumType({ name: 'DKIMResultOrderField', diff --git a/api-js/src/enums/dmarc-order-field.js b/api/src/enums/dmarc-order-field.js similarity index 94% rename from api-js/src/enums/dmarc-order-field.js rename to api/src/enums/dmarc-order-field.js index 0e5c4eb169..daf0a3688b 100644 --- a/api-js/src/enums/dmarc-order-field.js +++ b/api/src/enums/dmarc-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const DmarcOrderField = new GraphQLEnumType({ name: 'DmarcOrderField', diff --git a/api/src/enums/dmarc-phase.js b/api/src/enums/dmarc-phase.js new file mode 100644 index 0000000000..ca7694c6ea --- /dev/null +++ b/api/src/enums/dmarc-phase.js @@ -0,0 +1,24 @@ +import { GraphQLEnumType } from 'graphql' + +export const DmarcPhaseEnum = new GraphQLEnumType({ + name: 'DmarcPhaseEnum', + description: 'Phases of DMARC implementation.', + values: { + ASSESS: { + value: 'assess', + description: 'Assess domains and DMARC status.', + }, + DEPLOY: { + value: 'deploy', + description: 'Deploy SPF and DKIM records.', + }, + ENFORCE: { + value: 'enforce', + description: 'Enforce DMARC policies.', + }, + MAINTAIN: { + value: 'maintain', + description: 'Maintain DMARC and update records.', + }, + }, +}) diff --git a/api-js/src/enums/dmarc-summary-order-field.js b/api/src/enums/dmarc-summary-order-field.js similarity index 97% rename from api-js/src/enums/dmarc-summary-order-field.js rename to api/src/enums/dmarc-summary-order-field.js index d122ac6cc0..6474dfda1b 100644 --- a/api-js/src/enums/dmarc-summary-order-field.js +++ b/api/src/enums/dmarc-summary-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const DmarcSummaryOrderField = new GraphQLEnumType({ name: 'DmarcSummaryOrderField', diff --git a/api/src/enums/dns-order-field.js b/api/src/enums/dns-order-field.js new file mode 100644 index 0000000000..209f5ee0e6 --- /dev/null +++ b/api/src/enums/dns-order-field.js @@ -0,0 +1,12 @@ +import {GraphQLEnumType} from 'graphql' + +export const DnsOrderField = new GraphQLEnumType({ + name: 'DNSOrderField', + description: 'Properties by which DNS connections can be ordered.', + values: { + TIMESTAMP: { + value: 'timestamp', + description: 'Order DNS edges by timestamp.', + }, + }, +}) diff --git a/api/src/enums/domain-filter-category.js b/api/src/enums/domain-filter-category.js new file mode 100644 index 0000000000..bc22325986 --- /dev/null +++ b/api/src/enums/domain-filter-category.js @@ -0,0 +1,29 @@ +import { GraphQLEnumType } from 'graphql' +import { DomainOrderField } from './domain-order-field' + +export const DomainFilterCategory = new GraphQLEnumType({ + name: 'DomainFilterCategory', + description: 'Properties by which domain connections can be filtered.', + values: { + ...DomainOrderField.getValues().reduce((acc, { name, value, description }) => { + acc[name] = { value, description } + return acc + }, {}), + TAGS: { + value: 'tags', + description: 'Order domains by tags.', + }, + ASSET_STATE: { + value: 'asset-state', + description: 'Order domains by asset state.', + }, + GUIDANCE_TAG: { + value: 'guidance-tag', + description: 'Scanner findings.', + }, + DMARC_PHASE: { + value: 'dmarc-phase', + description: 'DMARC phase.', + }, + }, +}) diff --git a/api/src/enums/domain-order-field.js b/api/src/enums/domain-order-field.js new file mode 100644 index 0000000000..ea8c60b807 --- /dev/null +++ b/api/src/enums/domain-order-field.js @@ -0,0 +1,52 @@ +import { GraphQLEnumType } from 'graphql' + +export const DomainOrderField = new GraphQLEnumType({ + name: 'DomainOrderField', + description: 'Properties by which domain connections can be ordered.', + values: { + CERTIFICATES_STATUS: { + value: 'certificates-status', + description: 'Order domains by certificates status.', + }, + CIPHERS_STATUS: { + value: 'ciphers-status', + description: 'Order domains by ciphers status.', + }, + CURVES_STATUS: { + value: 'curves-status', + description: 'Order domains by curves status.', + }, + DOMAIN: { + value: 'domain', + description: 'Order domains by domain.', + }, + DKIM_STATUS: { + value: 'dkim-status', + description: 'Order domains by dkim status.', + }, + DMARC_STATUS: { + value: 'dmarc-status', + description: 'Order domains by dmarc status.', + }, + HTTPS_STATUS: { + value: 'https-status', + description: 'Order domains by https status.', + }, + HSTS_STATUS: { + value: 'hsts-status', + description: 'Order domains by hsts status.', + }, + POLICY_STATUS: { + value: 'policy-status', + description: 'Order domains by ITPIN policy status.', + }, + PROTOCOLS_STATUS: { + value: 'protocols-status', + description: 'Order domains by protocols status.', + }, + SPF_STATUS: { + value: 'spf-status', + description: 'Order domains by spf status.', + }, + }, +}) diff --git a/api/src/enums/domain-removal-reason.js b/api/src/enums/domain-removal-reason.js new file mode 100644 index 0000000000..bec3d297d5 --- /dev/null +++ b/api/src/enums/domain-removal-reason.js @@ -0,0 +1,16 @@ +import { GraphQLEnumType } from 'graphql' + +export const DomainRemovalReasonEnum = new GraphQLEnumType({ + name: 'DomainRemovalReasonEnum', + values: { + NONEXISTENT: { + value: 'nonexistent', + description: 'Domain does not exist.', + }, + WRONG_ORG: { + value: 'wrong_org', + description: 'Domain was in the incorrect organization.', + }, + }, + description: 'Reason why a domain was removed from an organization.', +}) diff --git a/api/src/enums/domain-tag-label.js b/api/src/enums/domain-tag-label.js new file mode 100644 index 0000000000..6ae43ca99f --- /dev/null +++ b/api/src/enums/domain-tag-label.js @@ -0,0 +1,49 @@ +import { GraphQLEnumType } from 'graphql' + +export const DomainTagLabel = new GraphQLEnumType({ + name: 'DomainTagLabel', + values: { + ARCHIVED: { + value: 'archived', + description: 'English label for tagging domains that are archived.', + }, + NXDOMAIN: { + value: 'nxdomain', + description: 'Label for tagging domains that have an rcode status of NXDOMAIN.', + }, + BLOCKED: { + value: 'blocked', + description: 'Label for tagging domains that are possibly blocked by a firewall.', + }, + WILDCARD_SIBLING: { + value: 'wildcard-sibling', + description: 'Label for tagging domains that have a wildcard sibling.', + }, + WILDCARD_ENTRY: { + value: 'wildcard-entry', + description: 'Label for tagging domains that have a wildcard entry.', + }, + SCAN_PENDING: { + value: 'scan-pending', + description: 'Label for tagging domains that have a pending web scan.', + }, + CVE_DETECTED: { + value: 'cve-detected', + description: 'Label for tagging domains that have vulnerabilities.', + }, + CVD_ENROLLED: { + value: 'cvd-enrolled', + description: 'Label for tagging domains that are enrolled in the Coordinated Vulnerability Disclosure program.', + }, + CVD_PENDING: { + value: 'cvd-pending', + description: + 'Label for tagging domains that are pending enrollment in the Coordinated Vulnerability Disclosure program.', + }, + CVD_DENY: { + value: 'cvd-deny', + description: 'Label for tagging domains that have been explicitly excluded from the Coordinated Vulnerability Disclosure program.', + }, + }, + description: 'An enum used to assign and test user-generated domain tags', +}) diff --git a/api/src/enums/enrollment-status.js b/api/src/enums/enrollment-status.js new file mode 100644 index 0000000000..1ab1c2a334 --- /dev/null +++ b/api/src/enums/enrollment-status.js @@ -0,0 +1,24 @@ +import { GraphQLEnumType } from 'graphql' + +export const EnrollmentStatusEnums = new GraphQLEnumType({ + name: 'EnrollmentStatusEnums', + values: { + ENROLLED: { + value: 'enrolled', + description: 'The asset is enrolled in the CVD program and eligible for coordinated vulnerability disclosure.', + }, + PENDING: { + value: 'pending', + description: 'The asset enrollment is pending approval for the CVD program.', + }, + NOT_ENROLLED: { + value: 'not-enrolled', + description: 'The asset is not enrolled in the CVD program.', + }, + DENY: { + value: 'deny', + description: 'The asset has been explicitly excluded from the CVD program.', + }, + }, + description: 'Enumerates the possible enrollment states for the Coordinated Vulnerability Disclosure (CVD) program.', +}) diff --git a/api/src/enums/filter-comparisons.js b/api/src/enums/filter-comparisons.js new file mode 100644 index 0000000000..e59dcf32af --- /dev/null +++ b/api/src/enums/filter-comparisons.js @@ -0,0 +1,16 @@ +import { GraphQLEnumType } from 'graphql' + +export const ComparisonEnums = new GraphQLEnumType({ + name: 'ComparisonEnums', + values: { + EQUAL: { + value: '==', + description: '', + }, + NOT_EQUAL: { + value: '!=', + description: '', + }, + }, + description: '', +}) diff --git a/api-js/src/enums/guidance-tag-order-field.js b/api/src/enums/guidance-tag-order-field.js similarity index 83% rename from api-js/src/enums/guidance-tag-order-field.js rename to api/src/enums/guidance-tag-order-field.js index 092398e983..8883a0f160 100644 --- a/api-js/src/enums/guidance-tag-order-field.js +++ b/api/src/enums/guidance-tag-order-field.js @@ -16,5 +16,9 @@ export const GuidanceTagOrderField = new GraphQLEnumType({ value: 'guidance', description: 'Order guidance tag edges by tag guidance.', }, + TAG_COUNT: { + value: 'tag-count', + description: 'Order guidance tag edges by tag count.', + }, }, }) diff --git a/api-js/src/enums/https-order-field.js b/api/src/enums/https-order-field.js similarity index 94% rename from api-js/src/enums/https-order-field.js rename to api/src/enums/https-order-field.js index 119c5df118..586744370d 100644 --- a/api-js/src/enums/https-order-field.js +++ b/api/src/enums/https-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const HttpsOrderField = new GraphQLEnumType({ name: 'HTTPSOrderField', diff --git a/api/src/enums/index.js b/api/src/enums/index.js new file mode 100644 index 0000000000..3cf345b67e --- /dev/null +++ b/api/src/enums/index.js @@ -0,0 +1,37 @@ +export * from './affiliation-org-order-field' +export * from './affiliation-user-order-field' +export * from './asset-state' +export * from './user-order-field' +export * from './dkim-order-field' +export * from './dkim-result-order-field' +export * from './dmarc-order-field' +export * from './dmarc-summary-order-field' +export * from './dns-order-field' +export * from './domain-order-field' +export * from './domain-removal-reason' +export * from './domain-tag-label' +export * from './filter-comparisons' +export * from './guidance-tag-order-field' +export * from './https-order-field' +export * from './invitation-roles' +export * from './languages' +export * from './log-order-field' +export * from './order-direction' +export * from './organization-order-field' +export * from './period' +export * from './roles' +export * from './scan-types' +export * from './severity' +export * from './spf-order-field' +export * from './ssl-order-field' +export * from './status' +export * from './tfa-send-method' +export * from './verified-domain-order-field' +export * from './verified-organization-order-field' +export * from './web-order-field' +export * from './tag-ownership' +export * from './system-filter-value' +export * from './domain-filter-category' +export * from './dmarc-phase' +export * from './enrollment-status' +export * from './cvd-requirement' diff --git a/api/src/enums/invitation-roles.js b/api/src/enums/invitation-roles.js new file mode 100644 index 0000000000..fb065f7f8b --- /dev/null +++ b/api/src/enums/invitation-roles.js @@ -0,0 +1,26 @@ +import { GraphQLEnumType } from 'graphql' + +export const InvitationRoleEnums = new GraphQLEnumType({ + name: 'InvitationRoleEnums', + values: { + USER: { + value: 'user', + description: 'A user who has been given access to view an organization.', + }, + ADMIN: { + value: 'admin', + description: + 'A user who has the same access as a user write account, but can define new user read/write accounts.', + }, + OWNER: { + value: 'owner', + description: + 'A user who has the same access as an admin, but can define new admins, and delete the organization.', + }, + SUPER_ADMIN: { + value: 'super_admin', + description: 'A user who has the same access as an admin, but can define new admins.', + }, + }, + description: 'An enum used when inviting users to an organization to assign their role.', +}) diff --git a/api-js/src/enums/languages.js b/api/src/enums/languages.js similarity index 90% rename from api-js/src/enums/languages.js rename to api/src/enums/languages.js index 2dc1d76c26..ba41aa5c4c 100644 --- a/api-js/src/enums/languages.js +++ b/api/src/enums/languages.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const LanguageEnums = new GraphQLEnumType({ name: 'LanguageEnums', diff --git a/api/src/enums/log-order-field.js b/api/src/enums/log-order-field.js new file mode 100644 index 0000000000..6d8c50aac6 --- /dev/null +++ b/api/src/enums/log-order-field.js @@ -0,0 +1,20 @@ +import { GraphQLEnumType } from 'graphql' + +export const LogOrderField = new GraphQLEnumType({ + name: 'LogOrderField', + description: 'Properties by which domain connections can be ordered.', + values: { + TIMESTAMP: { + value: 'timestamp', + description: 'Order logs by timestamp.', + }, + INITIATED_BY: { + value: 'initiated_by', + description: "Order logs by initiant's username.", + }, + RESOURCE_NAME: { + value: 'resource_name', + description: 'Order logs by name of targeted resource.', + }, + }, +}) diff --git a/api-js/src/enums/order-direction.js b/api/src/enums/order-direction.js similarity index 91% rename from api-js/src/enums/order-direction.js rename to api/src/enums/order-direction.js index b03e2cfbce..ebde2fc76b 100644 --- a/api-js/src/enums/order-direction.js +++ b/api/src/enums/order-direction.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const OrderDirection = new GraphQLEnumType({ name: 'OrderDirection', diff --git a/api-js/src/enums/organization-order-field.js b/api/src/enums/organization-order-field.js similarity index 97% rename from api-js/src/enums/organization-order-field.js rename to api/src/enums/organization-order-field.js index b4fa30bf71..0176790b7e 100644 --- a/api-js/src/enums/organization-order-field.js +++ b/api/src/enums/organization-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const OrganizationOrderField = new GraphQLEnumType({ name: 'OrganizationOrderField', diff --git a/api-js/src/enums/period.js b/api/src/enums/period.js similarity index 88% rename from api-js/src/enums/period.js rename to api/src/enums/period.js index 1ffe28f90b..b215298965 100644 --- a/api-js/src/enums/period.js +++ b/api/src/enums/period.js @@ -55,6 +55,14 @@ export const PeriodEnums = new GraphQLEnumType({ value: 'thirtyDays', description: 'The last 30 days.', }, + LASTYEAR: { + value: 'lastYear', + description: 'The last year.', + }, + YTD: { + value: 'yearToDate', + description: 'The year to date.', + }, }, description: 'An enum used to select information from the dmarc-report-api.', }) diff --git a/api/src/enums/resource-type.js b/api/src/enums/resource-type.js new file mode 100644 index 0000000000..6b37fe045a --- /dev/null +++ b/api/src/enums/resource-type.js @@ -0,0 +1,24 @@ +import { GraphQLEnumType } from 'graphql' + +export const ResourceTypeEnums = new GraphQLEnumType({ + name: 'ResourceTypeEnums', + values: { + USER: { + value: 'user', + description: 'A user account affiliated with an organization.', + }, + ORGANIZATION: { + value: 'organization', + description: 'An organization.', + }, + DOMAIN: { + value: 'domain', + description: 'A domain affiliated with an organization.', + }, + TAG: { + value: 'tag', + description: 'A tag used for organizing domains.', + }, + }, + description: 'Keywords used to describe resources that can be modified.', +}) diff --git a/api/src/enums/roles.js b/api/src/enums/roles.js new file mode 100644 index 0000000000..a9878aaaa0 --- /dev/null +++ b/api/src/enums/roles.js @@ -0,0 +1,34 @@ +import { GraphQLEnumType } from 'graphql' + +export const RoleEnums = new GraphQLEnumType({ + name: 'RoleEnums', + values: { + PENDING: { + value: 'pending', + description: 'A user who has requested an invite to an organization.', + }, + USER: { + value: 'user', + description: 'A user who has been given access to view an organization.', + }, + ADMIN: { + value: 'admin', + description: + 'A user who has the same access as a user write account, but can define new user read/write accounts.', + }, + OWNER: { + value: 'owner', + description: + 'A user who has the same access as an admin, but can define new admins, and delete the organization.', + }, + SUPER_ADMIN: { + value: 'super_admin', + description: 'A user who has the same access as an admin, but can define new admins.', + }, + SERVICE: { + value: 'service', + description: 'An internal service used by Tracker to make changes to organizational data.', + }, + }, + description: 'An enum used to assign, and test users roles.', +}) diff --git a/api-js/src/enums/scan-types.js b/api/src/enums/scan-types.js similarity index 91% rename from api-js/src/enums/scan-types.js rename to api/src/enums/scan-types.js index ce52c85d2c..fab976e1eb 100644 --- a/api-js/src/enums/scan-types.js +++ b/api/src/enums/scan-types.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const ScanTypeEnums = new GraphQLEnumType({ name: 'ScanTypeEnums', diff --git a/api/src/enums/severity.js b/api/src/enums/severity.js new file mode 100644 index 0000000000..c9c80f2b17 --- /dev/null +++ b/api/src/enums/severity.js @@ -0,0 +1,25 @@ +import {GraphQLEnumType} from 'graphql' + +export const SeverityEnum = new GraphQLEnumType({ + name: 'SeverityEnum', + values: { + LOW: { + value: 'low', + description: 'If the given CVE is of a low level severity', + }, + MEDIUM: { + value: 'medium', + description: 'If the given CVE is of a medium level severity', + }, + HIGH: { + value: 'high', + description: 'If the given CVE is of a high level severity', + }, + CRITICAL: { + value: 'critical', + description: 'If the given cve is of a critical level severity', + }, + }, + description: + 'Enum used to inform front end of the level of severity of a given vulnerability for a domain', +}) diff --git a/api-js/src/enums/spf-order-field.js b/api/src/enums/spf-order-field.js similarity index 93% rename from api-js/src/enums/spf-order-field.js rename to api/src/enums/spf-order-field.js index 2a66069158..c02dcb34f3 100644 --- a/api-js/src/enums/spf-order-field.js +++ b/api/src/enums/spf-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const SpfOrderField = new GraphQLEnumType({ name: 'SPFOrderField', diff --git a/api-js/src/enums/ssl-order-field.js b/api/src/enums/ssl-order-field.js similarity index 97% rename from api-js/src/enums/ssl-order-field.js rename to api/src/enums/ssl-order-field.js index 77d0d0c2a1..e83fc69cc9 100644 --- a/api-js/src/enums/ssl-order-field.js +++ b/api/src/enums/ssl-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const SslOrderField = new GraphQLEnumType({ name: 'SSLOrderField', diff --git a/api-js/src/enums/status.js b/api/src/enums/status.js similarity index 93% rename from api-js/src/enums/status.js rename to api/src/enums/status.js index fdc0672a1e..aa409d139f 100644 --- a/api-js/src/enums/status.js +++ b/api/src/enums/status.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const StatusEnum = new GraphQLEnumType({ name: 'StatusEnum', diff --git a/api/src/enums/system-filter-value.js b/api/src/enums/system-filter-value.js new file mode 100644 index 0000000000..1d4f10f5bb --- /dev/null +++ b/api/src/enums/system-filter-value.js @@ -0,0 +1,23 @@ +import { GraphQLEnumType } from 'graphql' +import { StatusEnum } from './status' +import { DomainTagLabel } from './domain-tag-label' +import { AssetStateEnums } from './asset-state' +import { DmarcPhaseEnum } from './dmarc-phase' + +const getEnumValues = (enums) => { + return enums.getValues().reduce((acc, { name, value, description }) => { + acc[name] = { value, description } + return acc + }, {}) +} + +export const filterEnum = new GraphQLEnumType({ + name: 'SystemFilterValue', + values: { + ...getEnumValues(StatusEnum), + ...getEnumValues(DomainTagLabel), + ...getEnumValues(AssetStateEnums), + ...getEnumValues(DmarcPhaseEnum), + }, + description: 'Filter value from system-defined statuses or tags.', +}) diff --git a/api/src/enums/tag-ownership.js b/api/src/enums/tag-ownership.js new file mode 100644 index 0000000000..90e7ada994 --- /dev/null +++ b/api/src/enums/tag-ownership.js @@ -0,0 +1,20 @@ +import { GraphQLEnumType } from 'graphql' + +export const TagOwnershipEnums = new GraphQLEnumType({ + name: 'TagOwnershipEnums', + values: { + GLOBAL: { + value: 'global', + description: 'Visible to all users, not affiliated with any organization.', + }, + ORG: { + value: 'org', + description: 'Visible to all users affiliated with an organization.', + }, + PENDING: { + value: 'pending', + description: 'Visible to admins, but not yet affiliated with any organization.', + }, + }, + description: 'Enum representing the ownership of a tag, determining its visibility and affiliation.', +}) diff --git a/api-js/src/enums/tfa-send-method.js b/api/src/enums/tfa-send-method.js similarity index 91% rename from api-js/src/enums/tfa-send-method.js rename to api/src/enums/tfa-send-method.js index a0e9179cca..27958d7ce0 100644 --- a/api-js/src/enums/tfa-send-method.js +++ b/api/src/enums/tfa-send-method.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const TfaSendMethodEnum = new GraphQLEnumType({ name: 'TFASendMethodEnum', diff --git a/api/src/enums/user-action.js b/api/src/enums/user-action.js new file mode 100644 index 0000000000..e5160ffc85 --- /dev/null +++ b/api/src/enums/user-action.js @@ -0,0 +1,36 @@ +import { GraphQLEnumType } from 'graphql' + +export const UserActionEnums = new GraphQLEnumType({ + name: 'UserActionEnums', + values: { + CREATE: { + value: 'create', + description: 'A new resource was created.', + }, + DELETE: { + value: 'delete', + description: 'A resource was deleted.', + }, + ADD: { + value: 'add', + description: 'An affiliation between resources was created.', + }, + UPDATE: { + value: 'update', + description: 'Properties of a resource or affiliation were modified.', + }, + REMOVE: { + value: 'remove', + description: 'An affiliation between resources was deleted.', + }, + SCAN: { + value: 'scan', + description: 'A scan was requested on a resource.', + }, + EXPORT: { + value: 'export', + description: 'A resource was exported.', + }, + }, + description: 'Describes actions performed by users to modify resources.', +}) diff --git a/api/src/enums/user-order-field.js b/api/src/enums/user-order-field.js new file mode 100644 index 0000000000..e4f428e9cd --- /dev/null +++ b/api/src/enums/user-order-field.js @@ -0,0 +1,28 @@ +import { GraphQLEnumType } from 'graphql' + +export const UserOrderField = new GraphQLEnumType({ + name: 'UserOrderField', + description: 'Properties by which affiliation connections can be ordered.', + values: { + USER_USERNAME: { + value: 'user-username', + description: 'Order affiliation edges by username.', + }, + USER_DISPLAYNAME: { + value: 'user-displayName', + description: 'Order affiliation edges by displayName.', + }, + USER_EMAIL_VALIDATED: { + value: 'user-emailValidated', + description: 'Order affiliation edges by user verification status.', + }, + USER_INSIDER: { + value: 'user-insider', + description: 'Order affiliation edges by user insider status.', + }, + USER_AFFILIATIONS_COUNT: { + value: 'user-affiliations-totalCount', + description: 'Order affiliation edges by amount of total affiliations.', + }, + }, +}) diff --git a/api-js/src/enums/verified-domain-order-field.js b/api/src/enums/verified-domain-order-field.js similarity index 96% rename from api-js/src/enums/verified-domain-order-field.js rename to api/src/enums/verified-domain-order-field.js index 4d00139762..a0bcc20d2c 100644 --- a/api-js/src/enums/verified-domain-order-field.js +++ b/api/src/enums/verified-domain-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const VerifiedDomainOrderField = new GraphQLEnumType({ name: 'VerifiedDomainOrderField', diff --git a/api-js/src/enums/verified-organization-order-field.js b/api/src/enums/verified-organization-order-field.js similarity index 97% rename from api-js/src/enums/verified-organization-order-field.js rename to api/src/enums/verified-organization-order-field.js index 0f1091df5d..d358e64834 100644 --- a/api-js/src/enums/verified-organization-order-field.js +++ b/api/src/enums/verified-organization-order-field.js @@ -1,4 +1,4 @@ -import { GraphQLEnumType } from 'graphql' +import {GraphQLEnumType} from 'graphql' export const VerifiedOrganizationOrderField = new GraphQLEnumType({ name: 'VerifiedOrganizationOrderField', diff --git a/api/src/enums/web-order-field.js b/api/src/enums/web-order-field.js new file mode 100644 index 0000000000..78683ab59e --- /dev/null +++ b/api/src/enums/web-order-field.js @@ -0,0 +1,12 @@ +import {GraphQLEnumType} from 'graphql' + +export const WebOrderField = new GraphQLEnumType({ + name: 'WebOrderField', + description: 'Properties by which web connections can be ordered.', + values: { + TIMESTAMP: { + value: 'timestamp', + description: 'Order web edges by timestamp.', + }, + }, +}) diff --git a/api/src/guidance-tag/data-source.js b/api/src/guidance-tag/data-source.js new file mode 100644 index 0000000000..913b78e361 --- /dev/null +++ b/api/src/guidance-tag/data-source.js @@ -0,0 +1,35 @@ +import { + loadAggregateGuidanceTagByTagId, + loadAggregateGuidanceTagConnectionsByTagId, + loadDkimGuidanceTagByTagId, + loadDkimGuidanceTagConnectionsByTagId, + loadDmarcGuidanceTagByTagId, + loadDmarcGuidanceTagConnectionsByTagId, + loadGuidanceTagByTagId, + loadGuidanceTagSummaryConnectionsByTagId, + loadHttpsGuidanceTagByTagId, + loadHttpsGuidanceTagConnectionsByTagId, + loadSpfGuidanceTagByTagId, + loadSpfGuidanceTagConnectionsByTagId, + loadSslGuidanceTagByTagId, + loadSslGuidanceTagConnectionsByTagId, +} from './loaders' + +export class GuidanceTagDataSource { + constructor({ query, userKey, i18n, language, cleanseInput }) { + this.byTagId = loadGuidanceTagByTagId({ query, userKey, i18n, language }) + this.summaryConnectionsByTagId = loadGuidanceTagSummaryConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + this.aggregateByTagId = loadAggregateGuidanceTagByTagId({ query, userKey, i18n, language }) + this.aggregateConnectionsByTagId = loadAggregateGuidanceTagConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + this.dkimByTagId = loadDkimGuidanceTagByTagId({ query, userKey, i18n, language }) + this.dkimConnectionsByTagId = loadDkimGuidanceTagConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + this.dmarcByTagId = loadDmarcGuidanceTagByTagId({ query, userKey, i18n, language }) + this.dmarcConnectionsByTagId = loadDmarcGuidanceTagConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + this.httpsByTagId = loadHttpsGuidanceTagByTagId({ query, userKey, i18n, language }) + this.httpsConnectionsByTagId = loadHttpsGuidanceTagConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + this.spfByTagId = loadSpfGuidanceTagByTagId({ query, userKey, i18n, language }) + this.spfConnectionsByTagId = loadSpfGuidanceTagConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + this.sslByTagId = loadSslGuidanceTagByTagId({ query, userKey, i18n, language }) + this.sslConnectionsByTagId = loadSslGuidanceTagConnectionsByTagId({ query, userKey, cleanseInput, i18n, language }) + } +} diff --git a/api/src/guidance-tag/index.js b/api/src/guidance-tag/index.js new file mode 100644 index 0000000000..52de3be3af --- /dev/null +++ b/api/src/guidance-tag/index.js @@ -0,0 +1,4 @@ +export * from './data-source' +export * from './inputs' +export * from './loaders' +export * from './objects' diff --git a/api/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js b/api/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js new file mode 100644 index 0000000000..a5d55dc818 --- /dev/null +++ b/api/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { guidanceTagOrder } from '../guidance-tag-order' +import { OrderDirection, GuidanceTagOrderField } from '../../../enums' + +describe('given the guidanceTagOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = guidanceTagOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = guidanceTagOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(GuidanceTagOrderField)) + }) + }) +}) diff --git a/api-js/src/guidance-tag/inputs/guidance-tag-order.js b/api/src/guidance-tag/inputs/guidance-tag-order.js similarity index 81% rename from api-js/src/guidance-tag/inputs/guidance-tag-order.js rename to api/src/guidance-tag/inputs/guidance-tag-order.js index d32253cdb0..1cdd0e443d 100644 --- a/api-js/src/guidance-tag/inputs/guidance-tag-order.js +++ b/api/src/guidance-tag/inputs/guidance-tag-order.js @@ -7,11 +7,11 @@ export const guidanceTagOrder = new GraphQLInputObjectType({ description: 'Ordering options for guidance tag connections.', fields: () => ({ field: { - type: GraphQLNonNull(GuidanceTagOrderField), + type: new GraphQLNonNull(GuidanceTagOrderField), description: 'The field to order guidance tags by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/guidance-tag/inputs/index.js b/api/src/guidance-tag/inputs/index.js similarity index 100% rename from api-js/src/guidance-tag/inputs/index.js rename to api/src/guidance-tag/inputs/index.js diff --git a/api/src/guidance-tag/loaders/__tests__/load-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-guidance-tags-connections.test.js new file mode 100644 index 0000000000..a2fafde691 --- /dev/null +++ b/api/src/guidance-tag/loaders/__tests__/load-guidance-tags-connections.test.js @@ -0,0 +1,77 @@ +const { loadGuidanceTagSummaryConnectionsByTagId } = require('../load-guidance-tags-connections') +const { createI18n } = require('../../../create-i18n') + +describe('loadGuidanceTagSummaryConnectionsByTagId', () => { + let query, userKey, cleanseInput, language + + const i18n = createI18n('en') + + beforeEach(() => { + query = jest.fn() + userKey = '1' + cleanseInput = jest.fn((input) => input) + language = 'en' + }) + + it('successfully retrieves data', async () => { + const guidanceTags = { tag1: 3, tag2: 5 } + const orderBy = { field: 'tag-id', direction: 'ASC' } + + query.mockResolvedValueOnce({ + next: jest.fn().mockResolvedValueOnce({ + guidanceTags: [ + { _key: '1', tagName: 'Tag 1' }, + { _key: '2', tagName: 'Tag 2' }, + ], + totalCount: 2, + }), + }) + + const loader = loadGuidanceTagSummaryConnectionsByTagId({ + query, + userKey, + cleanseInput, + i18n, + language, + }) + + const result = await loader({ guidanceTags, orderBy }) + expect(result).toEqual({ + guidanceTags: [ + { _key: '1', tagName: 'Tag 1' }, + { _key: '2', tagName: 'Tag 2' }, + ], + totalCount: 2, + }) + }) + + it('throws an error if there is a database error', async () => { + query.mockRejectedValueOnce(new Error('Database error')) + + const loader = loadGuidanceTagSummaryConnectionsByTagId({ + query, + userKey, + cleanseInput, + i18n, + language, + }) + + await expect(loader({ guidanceTags: {} })).rejects.toThrow('Unable to load guidance tag(s). Please try again.') + }) + + it('throws an error if there is a cursor error', async () => { + query.mockResolvedValueOnce({ + next: jest.fn().mockRejectedValueOnce(new Error('Cursor error')), + }) + + const loader = loadGuidanceTagSummaryConnectionsByTagId({ + query, + userKey, + cleanseInput, + i18n, + language, + }) + + await expect(loader({ guidanceTags: {} })).rejects.toThrow('Unable to load guidance tag(s). Please try again.') + }) +}) diff --git a/api/src/guidance-tag/loaders/index.js b/api/src/guidance-tag/loaders/index.js new file mode 100644 index 0000000000..88a9f34847 --- /dev/null +++ b/api/src/guidance-tag/loaders/index.js @@ -0,0 +1,14 @@ +export * from './load-aggregate-guidance-tags' +export * from './load-aggregate-guidance-tags-connections' +export * from './load-dkim-guidance-tags' +export * from './load-dkim-guidance-tags-connections' +export * from './load-dmarc-guidance-tags' +export * from './load-dmarc-guidance-tags-connections' +export * from './load-https-guidance-tags' +export * from './load-https-guidance-tags-connections' +export * from './load-spf-guidance-tags' +export * from './load-spf-guidance-tags-connections' +export * from './load-ssl-guidance-tags' +export * from './load-ssl-guidance-tags-connections' +export * from './load-guidance-tags' +export * from './load-guidance-tags-connections' diff --git a/api/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js new file mode 100644 index 0000000000..f06f540a22 --- /dev/null +++ b/api/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js @@ -0,0 +1,301 @@ +import {t} from '@lingui/macro' +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' + +export const loadAggregateGuidanceTagConnectionsByTagId = + ({query, userKey, cleanseInput, i18n, language}) => + async ({aggregateGuidanceTags, after, before, first, last, orderBy}) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` + } else { + let afterTemplateDirection = aql`<` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } + + afterVar = aql`LET afterVar = DOCUMENT(aggregateGuidanceTags, ${afterId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`afterVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, afterVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, afterVar).guidance` + } + + afterTemplate = aql` + FILTER ${tagField} ${afterTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` + } else { + let beforeTemplateDirection = aql`>` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(aggregateGuidanceTags, ${beforeId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`beforeVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, beforeVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, beforeVar).guidance` + } + + beforeTemplate = aql` + FILTER ${tagField} ${beforeTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAggregateGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAggregateGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])` + + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`<` + let hasPreviousPageDirection = aql`>` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } + + let tagField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + hasNextPageDocument = aql`LAST(retrievedAggregateGuidanceTags)._key` + hasPreviousPageDocument = aql`FIRST(retrievedAggregateGuidanceTags)._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + hasNextPageDocument = aql`LAST(retrievedAggregateGuidanceTags).tagName` + hasPreviousPageDocument = aql`FIRST(retrievedAggregateGuidanceTags).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + hasNextPageDocument = aql`LAST(retrievedAggregateGuidanceTags).guidance` + hasPreviousPageDocument = aql`FIRST(retrievedAggregateGuidanceTags).guidance` + } + + hasNextPageFilter = aql` + FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${tagField} == ${hasNextPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])) + ` + + hasPreviousPageFilter = aql` + FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${tagField} == ${hasPreviousPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedAggregateGuidanceTags)._key, "[a-z]+")[1])) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`tag._key ${orderBy.direction},` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` + } else if (orderBy.field === 'guidance') { + sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let aggregateGuidanceTagInfoCursor + try { + aggregateGuidanceTagInfoCursor = await query` + WITH aggregateGuidanceTags + + ${afterVar} + ${beforeVar} + + LET retrievedAggregateGuidanceTags = ( + FOR tag IN aggregateGuidanceTags + FILTER tag._key IN ${aggregateGuidanceTags} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ) + + LET hasNextPage = (LENGTH( + FOR tag IN aggregateGuidanceTags + FILTER tag._key IN ${aggregateGuidanceTags} + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR tag IN aggregateGuidanceTags + FILTER tag._key IN ${aggregateGuidanceTags} + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + RETURN { + "aggregateGuidanceTags": retrievedAggregateGuidanceTags, + "totalCount": LENGTH(${aggregateGuidanceTags}), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedAggregateGuidanceTags)._key, + "endKey": LAST(retrievedAggregateGuidanceTags)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load Aggregate guidance tag(s). Please try again.`), + ) + } + + let aggregateGuidanceTagInfo + try { + aggregateGuidanceTagInfo = await aggregateGuidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather tags in loadAggregateGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load Aggregate guidance tag(s). Please try again.`), + ) + } + + if (aggregateGuidanceTagInfo.aggregateGuidanceTags.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = aggregateGuidanceTagInfo.aggregateGuidanceTags.map((tag) => ({ + cursor: toGlobalId('guidanceTag', tag._key), + node: tag, + })) + + return { + edges, + totalCount: aggregateGuidanceTagInfo.totalCount, + pageInfo: { + hasNextPage: aggregateGuidanceTagInfo.hasNextPage, + hasPreviousPage: aggregateGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId( + 'guidanceTag', + aggregateGuidanceTagInfo.startKey, + ), + endCursor: toGlobalId('guidanceTag', aggregateGuidanceTagInfo.endKey), + }, + } + } diff --git a/api-js/src/guidance-tag/loaders/load-aggregate-guidance-tags.js b/api/src/guidance-tag/loaders/load-aggregate-guidance-tags.js similarity index 80% rename from api-js/src/guidance-tag/loaders/load-aggregate-guidance-tags.js rename to api/src/guidance-tag/loaders/load-aggregate-guidance-tags.js index 90cefe4cae..7fdeb1139e 100644 --- a/api-js/src/guidance-tag/loaders/load-aggregate-guidance-tags.js +++ b/api/src/guidance-tag/loaders/load-aggregate-guidance-tags.js @@ -1,12 +1,12 @@ -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' import DataLoader from 'dataloader' export const loadAggregateGuidanceTagByTagId = ({ - query, - userKey, - i18n, - language, -}) => + query, + userKey, + i18n, + language, + }) => new DataLoader(async (tags) => { let cursor try { diff --git a/api/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js new file mode 100644 index 0000000000..7bcb9a30a9 --- /dev/null +++ b/api/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js @@ -0,0 +1,304 @@ +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' +import {t} from '@lingui/macro' + +export const loadDkimGuidanceTagConnectionsByTagId = + ({query, userKey, cleanseInput, i18n, language}) => + async ({dkimGuidanceTags, after, before, first, last, orderBy}) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(dkimGuidanceTags, ${afterId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`afterVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, afterVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, afterVar).guidance` + } + + afterTemplate = aql` + FILTER ${tagField} ${afterTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` + } else { + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(dkimGuidanceTags, ${beforeId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`beforeVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, beforeVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, beforeVar).guidance` + } + + beforeTemplate = aql` + FILTER ${tagField} ${beforeTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDkimGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDkimGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection + let hasPreviousPageDirection + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let tagField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + hasNextPageDocument = aql`LAST(retrievedDkimGuidanceTags)._key` + hasPreviousPageDocument = aql`FIRST(retrievedDkimGuidanceTags)._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + hasNextPageDocument = aql`LAST(retrievedDkimGuidanceTags).tagName` + hasPreviousPageDocument = aql`FIRST(retrievedDkimGuidanceTags).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + hasNextPageDocument = aql`LAST(retrievedDkimGuidanceTags).guidance` + hasPreviousPageDocument = aql`FIRST(retrievedDkimGuidanceTags).guidance` + } + + hasNextPageFilter = aql` + FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${tagField} == ${hasNextPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])) + ` + + hasPreviousPageFilter = aql` + FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${tagField} == ${hasPreviousPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDkimGuidanceTags)._key, "[a-z]+")[1])) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`tag._key ${orderBy.direction},` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` + } else if (orderBy.field === 'guidance') { + sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let dkimGuidanceTagInfoCursor + try { + dkimGuidanceTagInfoCursor = await query` + WITH dkimGuidanceTags + + ${afterVar} + ${beforeVar} + + LET retrievedDkimGuidanceTags = ( + FOR tag IN dkimGuidanceTags + FILTER tag._key IN ${dkimGuidanceTags} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ) + + LET hasNextPage = (LENGTH( + FOR tag IN dkimGuidanceTags + FILTER tag._key IN ${dkimGuidanceTags} + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR tag IN dkimGuidanceTags + FILTER tag._key IN ${dkimGuidanceTags} + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + RETURN { + "dkimGuidanceTags": retrievedDkimGuidanceTags, + "totalCount": LENGTH(${dkimGuidanceTags}), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedDkimGuidanceTags)._key, + "endKey": LAST(retrievedDkimGuidanceTags)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load DKIM guidance tag(s). Please try again.`), + ) + } + + let dkimGuidanceTagInfo + try { + dkimGuidanceTagInfo = await dkimGuidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadDkimGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load DKIM guidance tag(s). Please try again.`), + ) + } + + if (dkimGuidanceTagInfo.dkimGuidanceTags.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = dkimGuidanceTagInfo.dkimGuidanceTags.map((tag) => ({ + cursor: toGlobalId('guidanceTag', tag._key), + node: tag, + })) + + return { + edges, + totalCount: dkimGuidanceTagInfo.totalCount, + pageInfo: { + hasNextPage: dkimGuidanceTagInfo.hasNextPage, + hasPreviousPage: dkimGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', dkimGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', dkimGuidanceTagInfo.endKey), + }, + } + } diff --git a/api-js/src/guidance-tag/loaders/load-dkim-guidance-tags.js b/api/src/guidance-tag/loaders/load-dkim-guidance-tags.js similarity index 77% rename from api-js/src/guidance-tag/loaders/load-dkim-guidance-tags.js rename to api/src/guidance-tag/loaders/load-dkim-guidance-tags.js index 48866f9712..c791181c78 100644 --- a/api-js/src/guidance-tag/loaders/load-dkim-guidance-tags.js +++ b/api/src/guidance-tag/loaders/load-dkim-guidance-tags.js @@ -1,13 +1,13 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' export const loadDkimGuidanceTagByTagId = ({ - query, - userKey, - i18n, - language, -}) => - new DataLoader(async (tags) => { + query, + userKey, + i18n, + language, + }) => + async ({tags = []}) => { + let cursor try { cursor = await query` @@ -15,7 +15,7 @@ export const loadDkimGuidanceTagByTagId = ({ FOR tag IN dkimGuidanceTags FILTER tag._key IN ${tags} RETURN MERGE( - { + { _id: tag._id, _key: tag._key, _rev: tag._rev, @@ -50,4 +50,4 @@ export const loadDkimGuidanceTagByTagId = ({ } return tags.map((tag) => tagMap[tag]) - }) + } diff --git a/api/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js new file mode 100644 index 0000000000..7e81019e41 --- /dev/null +++ b/api/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js @@ -0,0 +1,304 @@ +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' +import {t} from '@lingui/macro' + +export const loadDmarcGuidanceTagConnectionsByTagId = + ({query, userKey, cleanseInput, i18n, language}) => + async ({dmarcGuidanceTags, after, before, first, last, orderBy}) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(dmarcGuidanceTags, ${afterId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`afterVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, afterVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, afterVar).guidance` + } + + afterTemplate = aql` + FILTER ${tagField} ${afterTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` + } else { + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(dmarcGuidanceTags, ${beforeId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`beforeVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, beforeVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, beforeVar).guidance` + } + + beforeTemplate = aql` + FILTER ${tagField} ${beforeTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDmarcGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection + let hasPreviousPageDirection + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let tagField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + hasNextPageDocument = aql`LAST(retrievedDmarcGuidanceTags)._key` + hasPreviousPageDocument = aql`FIRST(retrievedDmarcGuidanceTags)._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + hasNextPageDocument = aql`LAST(retrievedDmarcGuidanceTags).tagName` + hasPreviousPageDocument = aql`FIRST(retrievedDmarcGuidanceTags).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + hasNextPageDocument = aql`LAST(retrievedDmarcGuidanceTags).guidance` + hasPreviousPageDocument = aql`FIRST(retrievedDmarcGuidanceTags).guidance` + } + + hasNextPageFilter = aql` + FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${tagField} == ${hasNextPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])) + ` + + hasPreviousPageFilter = aql` + FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${tagField} == ${hasPreviousPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedDmarcGuidanceTags)._key, "[a-z]+")[1])) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`tag._key ${orderBy.direction},` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` + } else if (orderBy.field === 'guidance') { + sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let dmarcGuidanceTagInfoCursor + try { + dmarcGuidanceTagInfoCursor = await query` + WITH dmarcGuidanceTags + + ${afterVar} + ${beforeVar} + + LET retrievedDmarcGuidanceTags = ( + FOR tag IN dmarcGuidanceTags + FILTER tag._key IN ${dmarcGuidanceTags} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ) + + LET hasNextPage = (LENGTH( + FOR tag IN dmarcGuidanceTags + FILTER tag._key IN ${dmarcGuidanceTags} + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR tag IN dmarcGuidanceTags + FILTER tag._key IN ${dmarcGuidanceTags} + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + RETURN { + "dmarcGuidanceTags": retrievedDmarcGuidanceTags, + "totalCount": LENGTH(${dmarcGuidanceTags}), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedDmarcGuidanceTags)._key, + "endKey": LAST(retrievedDmarcGuidanceTags)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load DMARC guidance tag(s). Please try again.`), + ) + } + + let dmarcGuidanceTagInfo + try { + dmarcGuidanceTagInfo = await dmarcGuidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadDmarcGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load DMARC guidance tag(s). Please try again.`), + ) + } + + if (dmarcGuidanceTagInfo.dmarcGuidanceTags.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = dmarcGuidanceTagInfo.dmarcGuidanceTags.map((tag) => ({ + cursor: toGlobalId('guidanceTag', tag._key), + node: tag, + })) + + return { + edges, + totalCount: dmarcGuidanceTagInfo.totalCount, + pageInfo: { + hasNextPage: dmarcGuidanceTagInfo.hasNextPage, + hasPreviousPage: dmarcGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', dmarcGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', dmarcGuidanceTagInfo.endKey), + }, + } + } diff --git a/api-js/src/guidance-tag/loaders/load-dmarc-guidance-tags.js b/api/src/guidance-tag/loaders/load-dmarc-guidance-tags.js similarity index 78% rename from api-js/src/guidance-tag/loaders/load-dmarc-guidance-tags.js rename to api/src/guidance-tag/loaders/load-dmarc-guidance-tags.js index 343f27099f..383539e261 100644 --- a/api-js/src/guidance-tag/loaders/load-dmarc-guidance-tags.js +++ b/api/src/guidance-tag/loaders/load-dmarc-guidance-tags.js @@ -1,13 +1,13 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' export const loadDmarcGuidanceTagByTagId = ({ - query, - userKey, - i18n, - language, -}) => - new DataLoader(async (tags) => { + query, + userKey, + i18n, + language, + }) => + async ({tags = []}) => { + let cursor try { cursor = await query` @@ -50,4 +50,4 @@ export const loadDmarcGuidanceTagByTagId = ({ } return tags.map((tag) => tagMap[tag]) - }) + } diff --git a/api/src/guidance-tag/loaders/load-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-guidance-tags-connections.js new file mode 100644 index 0000000000..ecb08d0c1d --- /dev/null +++ b/api/src/guidance-tag/loaders/load-guidance-tags-connections.js @@ -0,0 +1,75 @@ +import { aql } from 'arangojs' +import { t } from '@lingui/macro' + +export const loadGuidanceTagSummaryConnectionsByTagId = + ({ query, userKey, i18n, language }) => + async ({ guidanceTags, orderBy }) => { + const tagIds = Object.keys(guidanceTags) + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`SORT tag._key ${orderBy.direction}` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`SORT TRANSLATE(${language}, tag).tagName ${orderBy.direction}` + } else if (orderBy.field === 'guidance') { + sortByField = aql`SORT TRANSLATE(${language}, tag).guidance ${orderBy.direction}` + } else if (orderBy.field === 'tag-count') { + sortByField = aql`SORT TRANSLATE(tag._key, tagSummaries) ${orderBy.direction}` + } + } + + let guidanceTagInfoCursor + try { + guidanceTagInfoCursor = await query` + WITH guidanceTags + LET tagSummaries = (${guidanceTags}) + LET retrievedGuidanceTags = ( + FOR tag IN guidanceTags + FILTER tag._key IN ${tagIds} + ${sortByField} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key, + count: TRANSLATE(tag._key, tagSummaries) + }, + TRANSLATE(${language}, tag) + ) + ) + RETURN { + "guidanceTags": retrievedGuidanceTags, + "totalCount": LENGTH(${tagIds}), + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather guidance tags in loadGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load guidance tag(s). Please try again.`)) + } + + let guidanceTagInfo + try { + guidanceTagInfo = await guidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather guidance tags in loadGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load guidance tag(s). Please try again.`)) + } + + if (guidanceTagInfo.guidanceTags.length === 0) { + return { + guidanceTags: [], + totalCount: 0, + } + } + + return guidanceTagInfo + } diff --git a/api/src/guidance-tag/loaders/load-guidance-tags.js b/api/src/guidance-tag/loaders/load-guidance-tags.js new file mode 100644 index 0000000000..3245fa6312 --- /dev/null +++ b/api/src/guidance-tag/loaders/load-guidance-tags.js @@ -0,0 +1,40 @@ +import { t } from '@lingui/macro' + +export const loadGuidanceTagByTagId = + ({ query, userKey, i18n, language }) => + async ({ tags = [] }) => { + let cursor + try { + cursor = await query` + WITH guidanceTags + FOR tag IN guidanceTags + FILTER tag._key IN ${tags} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadGuidanceTagByTagId: ${err}`) + throw new Error(i18n._(t`Unable to find guidance tag(s). Please try again.`)) + } + + const tagMap = {} + try { + await cursor.forEach((tag) => { + tagMap[tag._key] = tag + }) + } catch (err) { + console.error(`Cursor error occurred when user: ${userKey} running loadGuidanceTagByTagId: ${err}`) + throw new Error(i18n._(t`Unable to find guidance tag(s). Please try again.`)) + } + + return tags.map((tag) => tagMap[tag]) + } diff --git a/api/src/guidance-tag/loaders/load-https-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-https-guidance-tags-connections.js new file mode 100644 index 0000000000..8cd3f5530e --- /dev/null +++ b/api/src/guidance-tag/loaders/load-https-guidance-tags-connections.js @@ -0,0 +1,304 @@ +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' +import {t} from '@lingui/macro' + +export const loadHttpsGuidanceTagConnectionsByTagId = + ({query, userKey, cleanseInput, i18n, language}) => + async ({httpsGuidanceTags, after, before, first, last, orderBy}) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(httpsGuidanceTags, ${afterId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`afterVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, afterVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, afterVar).guidance` + } + + afterTemplate = aql` + FILTER ${tagField} ${afterTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` + } else { + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(httpsGuidanceTags, ${beforeId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`beforeVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, beforeVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, beforeVar).guidance` + } + + beforeTemplate = aql` + FILTER ${tagField} ${beforeTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadHttpsGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadHttpsGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection + let hasPreviousPageDirection + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let tagField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + hasNextPageDocument = aql`LAST(retrievedHttpsGuidanceTags)._key` + hasPreviousPageDocument = aql`FIRST(retrievedHttpsGuidanceTags)._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + hasNextPageDocument = aql`LAST(retrievedHttpsGuidanceTags).tagName` + hasPreviousPageDocument = aql`FIRST(retrievedHttpsGuidanceTags).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + hasNextPageDocument = aql`LAST(retrievedHttpsGuidanceTags).guidance` + hasPreviousPageDocument = aql`FIRST(retrievedHttpsGuidanceTags).guidance` + } + + hasNextPageFilter = aql` + FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${tagField} == ${hasNextPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])) + ` + + hasPreviousPageFilter = aql` + FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${tagField} == ${hasPreviousPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedHttpsGuidanceTags)._key, "[a-z]+")[1])) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`tag._key ${orderBy.direction},` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` + } else if (orderBy.field === 'guidance') { + sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let httpsGuidanceTagInfoCursor + try { + httpsGuidanceTagInfoCursor = await query` + WITH httpsGuidanceTags + + ${afterVar} + ${beforeVar} + + LET retrievedHttpsGuidanceTags = ( + FOR tag IN httpsGuidanceTags + FILTER tag._key IN ${httpsGuidanceTags} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ) + + LET hasNextPage = (LENGTH( + FOR tag IN httpsGuidanceTags + FILTER tag._key IN ${httpsGuidanceTags} + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR tag IN httpsGuidanceTags + FILTER tag._key IN ${httpsGuidanceTags} + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + RETURN { + "httpsGuidanceTags": retrievedHttpsGuidanceTags, + "totalCount": LENGTH(${httpsGuidanceTags}), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedHttpsGuidanceTags)._key, + "endKey": LAST(retrievedHttpsGuidanceTags)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load HTTPS guidance tag(s). Please try again.`), + ) + } + + let httpsGuidanceTagInfo + try { + httpsGuidanceTagInfo = await httpsGuidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadHttpsGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load HTTPS guidance tag(s). Please try again.`), + ) + } + + if (httpsGuidanceTagInfo.httpsGuidanceTags.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = httpsGuidanceTagInfo.httpsGuidanceTags.map((tag) => ({ + cursor: toGlobalId('guidanceTag', tag._key), + node: tag, + })) + + return { + edges, + totalCount: httpsGuidanceTagInfo.totalCount, + pageInfo: { + hasNextPage: httpsGuidanceTagInfo.hasNextPage, + hasPreviousPage: httpsGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', httpsGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', httpsGuidanceTagInfo.endKey), + }, + } + } diff --git a/api-js/src/guidance-tag/loaders/load-https-guidance-tags.js b/api/src/guidance-tag/loaders/load-https-guidance-tags.js similarity index 78% rename from api-js/src/guidance-tag/loaders/load-https-guidance-tags.js rename to api/src/guidance-tag/loaders/load-https-guidance-tags.js index 40221395f1..a66d0e03ff 100644 --- a/api-js/src/guidance-tag/loaders/load-https-guidance-tags.js +++ b/api/src/guidance-tag/loaders/load-https-guidance-tags.js @@ -1,13 +1,12 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' export const loadHttpsGuidanceTagByTagId = ({ - query, - userKey, - i18n, - language, -}) => - new DataLoader(async (tags) => { + query, + userKey, + i18n, + language, + }) => + async ({tags = []}) => { let cursor try { cursor = await query` @@ -50,4 +49,4 @@ export const loadHttpsGuidanceTagByTagId = ({ } return tags.map((tag) => tagMap[tag]) - }) + } diff --git a/api/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js new file mode 100644 index 0000000000..20e50ebc49 --- /dev/null +++ b/api/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js @@ -0,0 +1,304 @@ +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' +import {t} from '@lingui/macro' + +export const loadSpfGuidanceTagConnectionsByTagId = + ({query, userKey, cleanseInput, i18n, language}) => + async ({spfGuidanceTags, after, before, first, last, orderBy}) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(spfGuidanceTags, ${afterId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`afterVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, afterVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, afterVar).guidance` + } + + afterTemplate = aql` + FILTER ${tagField} ${afterTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` + } else { + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(spfGuidanceTags, ${beforeId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`beforeVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, beforeVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, beforeVar).guidance` + } + + beforeTemplate = aql` + FILTER ${tagField} ${beforeTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSpfGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadSpfGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection + let hasPreviousPageDirection + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let tagField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + hasNextPageDocument = aql`LAST(retrievedSpfGuidanceTags)._key` + hasPreviousPageDocument = aql`FIRST(retrievedSpfGuidanceTags)._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + hasNextPageDocument = aql`LAST(retrievedSpfGuidanceTags).tagName` + hasPreviousPageDocument = aql`FIRST(retrievedSpfGuidanceTags).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + hasNextPageDocument = aql`LAST(retrievedSpfGuidanceTags).guidance` + hasPreviousPageDocument = aql`FIRST(retrievedSpfGuidanceTags).guidance` + } + + hasNextPageFilter = aql` + FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${tagField} == ${hasNextPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])) + ` + + hasPreviousPageFilter = aql` + FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${tagField} == ${hasPreviousPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSpfGuidanceTags)._key, "[a-z]+")[1])) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`tag._key ${orderBy.direction},` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` + } else if (orderBy.field === 'guidance') { + sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let spfGuidanceTagInfoCursor + try { + spfGuidanceTagInfoCursor = await query` + WITH spfGuidanceTags + + ${afterVar} + ${beforeVar} + + LET retrievedSpfGuidanceTags = ( + FOR tag IN spfGuidanceTags + FILTER tag._key IN ${spfGuidanceTags} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ) + + LET hasNextPage = (LENGTH( + FOR tag IN spfGuidanceTags + FILTER tag._key IN ${spfGuidanceTags} + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR tag IN spfGuidanceTags + FILTER tag._key IN ${spfGuidanceTags} + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + RETURN { + "spfGuidanceTags": retrievedSpfGuidanceTags, + "totalCount": LENGTH(${spfGuidanceTags}), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedSpfGuidanceTags)._key, + "endKey": LAST(retrievedSpfGuidanceTags)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load SPF guidance tag(s). Please try again.`), + ) + } + + let spfGuidanceTagInfo + try { + spfGuidanceTagInfo = await spfGuidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadSpfGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load SPF guidance tag(s). Please try again.`), + ) + } + + if (spfGuidanceTagInfo.spfGuidanceTags.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = spfGuidanceTagInfo.spfGuidanceTags.map((tag) => ({ + cursor: toGlobalId('guidanceTag', tag._key), + node: tag, + })) + + return { + edges, + totalCount: spfGuidanceTagInfo.totalCount, + pageInfo: { + hasNextPage: spfGuidanceTagInfo.hasNextPage, + hasPreviousPage: spfGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', spfGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', spfGuidanceTagInfo.endKey), + }, + } + } diff --git a/api-js/src/guidance-tag/loaders/load-spf-guidance-tags.js b/api/src/guidance-tag/loaders/load-spf-guidance-tags.js similarity index 85% rename from api-js/src/guidance-tag/loaders/load-spf-guidance-tags.js rename to api/src/guidance-tag/loaders/load-spf-guidance-tags.js index 484766c33c..1694328715 100644 --- a/api-js/src/guidance-tag/loaders/load-spf-guidance-tags.js +++ b/api/src/guidance-tag/loaders/load-spf-guidance-tags.js @@ -1,8 +1,7 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadSpfGuidanceTagByTagId = ({ query, userKey, i18n, language }) => - new DataLoader(async (tags) => { +export const loadSpfGuidanceTagByTagId = ({query, userKey, i18n, language}) => + async ({tags = []}) => { let cursor try { cursor = await query` @@ -45,4 +44,4 @@ export const loadSpfGuidanceTagByTagId = ({ query, userKey, i18n, language }) => } return tags.map((tag) => tagMap[tag]) - }) + } diff --git a/api/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js b/api/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js new file mode 100644 index 0000000000..cb68602cdb --- /dev/null +++ b/api/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js @@ -0,0 +1,304 @@ +import {aql} from 'arangojs' +import {fromGlobalId, toGlobalId} from 'graphql-relay' +import {t} from '@lingui/macro' + +export const loadSslGuidanceTagConnectionsByTagId = + ({query, userKey, cleanseInput, i18n, language}) => + async ({sslGuidanceTags, after, before, first, last, orderBy}) => { + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const {id: afterId} = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(sslGuidanceTags, ${afterId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`afterVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, afterVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, afterVar).guidance` + } + + afterTemplate = aql` + FILTER ${tagField} ${afterTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(${afterId}, "[a-z]+")[1])) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const {id: beforeId} = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])` + } else { + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(sslGuidanceTags, ${beforeId})` + + let tagField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + documentField = aql`beforeVar._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + documentField = aql`TRANSLATE(${language}, beforeVar).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + documentField = aql`TRANSLATE(${language}, beforeVar).guidance` + } + + beforeTemplate = aql` + FILTER ${tagField} ${beforeTemplateDirection} ${documentField} + OR (${tagField} == ${documentField} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(${beforeId}, "[a-z]+")[1])) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, + ), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + ), + ) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSslGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadSslGuidanceTagConnectionsByTagId.`, + ) + throw new Error( + i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), + ) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection + let hasPreviousPageDirection + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let tagField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + tagField = aql`tag._key` + hasNextPageDocument = aql`LAST(retrievedSslGuidanceTags)._key` + hasPreviousPageDocument = aql`FIRST(retrievedSslGuidanceTags)._key` + } else if (orderBy.field === 'tag-name') { + tagField = aql`TRANSLATE(${language}, tag).tagName` + hasNextPageDocument = aql`LAST(retrievedSslGuidanceTags).tagName` + hasPreviousPageDocument = aql`FIRST(retrievedSslGuidanceTags).tagName` + } else if (orderBy.field === 'guidance') { + tagField = aql`TRANSLATE(${language}, tag).guidance` + hasNextPageDocument = aql`LAST(retrievedSslGuidanceTags).guidance` + hasPreviousPageDocument = aql`FIRST(retrievedSslGuidanceTags).guidance` + } + + hasNextPageFilter = aql` + FILTER ${tagField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${tagField} == ${hasNextPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) > TO_NUMBER(REGEX_SPLIT(LAST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])) + ` + + hasPreviousPageFilter = aql` + FILTER ${tagField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${tagField} == ${hasPreviousPageDocument} + AND TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) < TO_NUMBER(REGEX_SPLIT(FIRST(retrievedSslGuidanceTags)._key, "[a-z]+")[1])) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'tag-id') { + sortByField = aql`tag._key ${orderBy.direction},` + } else if (orderBy.field === 'tag-name') { + sortByField = aql`TRANSLATE(${language}, tag).tagName ${orderBy.direction},` + } else if (orderBy.field === 'guidance') { + sortByField = aql`TRANSLATE(${language}, tag).guidance ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let sslGuidanceTagInfoCursor + try { + sslGuidanceTagInfoCursor = await query` + WITH sslGuidanceTags + + ${afterVar} + ${beforeVar} + + LET retrievedSslGuidanceTags = ( + FOR tag IN sslGuidanceTags + FILTER tag._key IN ${sslGuidanceTags} + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE( + { + _id: tag._id, + _key: tag._key, + _rev: tag._rev, + _type: "guidanceTag", + id: tag._key, + tagId: tag._key + }, + TRANSLATE(${language}, tag) + ) + ) + + LET hasNextPage = (LENGTH( + FOR tag IN sslGuidanceTags + FILTER tag._key IN ${sslGuidanceTags} + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + FOR tag IN sslGuidanceTags + FILTER tag._key IN ${sslGuidanceTags} + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(REGEX_SPLIT(tag._key, "[a-z]+")[1]) ${sortString} LIMIT 1 + RETURN tag + ) > 0 ? true : false) + + RETURN { + "sslGuidanceTags": retrievedSslGuidanceTags, + "totalCount": LENGTH(${sslGuidanceTags}), + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedSslGuidanceTags)._key, + "endKey": LAST(retrievedSslGuidanceTags)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load SSL guidance tag(s). Please try again.`), + ) + } + + let sslGuidanceTagInfo + try { + sslGuidanceTagInfo = await sslGuidanceTagInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadSslGuidanceTagConnectionsByTagId, error: ${err}`, + ) + throw new Error( + i18n._(t`Unable to load SSL guidance tag(s). Please try again.`), + ) + } + + if (sslGuidanceTagInfo.sslGuidanceTags.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = sslGuidanceTagInfo.sslGuidanceTags.map((tag) => ({ + cursor: toGlobalId('guidanceTag', tag._key), + node: tag, + })) + + return { + edges, + totalCount: sslGuidanceTagInfo.totalCount, + pageInfo: { + hasNextPage: sslGuidanceTagInfo.hasNextPage, + hasPreviousPage: sslGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', sslGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', sslGuidanceTagInfo.endKey), + }, + } + } diff --git a/api-js/src/guidance-tag/loaders/load-ssl-guidance-tags.js b/api/src/guidance-tag/loaders/load-ssl-guidance-tags.js similarity index 85% rename from api-js/src/guidance-tag/loaders/load-ssl-guidance-tags.js rename to api/src/guidance-tag/loaders/load-ssl-guidance-tags.js index 7605cad603..1092e31f54 100644 --- a/api-js/src/guidance-tag/loaders/load-ssl-guidance-tags.js +++ b/api/src/guidance-tag/loaders/load-ssl-guidance-tags.js @@ -1,8 +1,7 @@ -import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadSslGuidanceTagByTagId = ({ query, userKey, i18n, language }) => - new DataLoader(async (tags) => { +export const loadSslGuidanceTagByTagId = ({query, userKey, i18n, language}) => + async ({tags = []}) => { let cursor try { cursor = await query` @@ -45,4 +44,4 @@ export const loadSslGuidanceTagByTagId = ({ query, userKey, i18n, language }) => } return tags.map((tag) => tagMap[tag]) - }) + } diff --git a/api-js/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js b/api/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js similarity index 81% rename from api-js/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js rename to api/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js index fe49ebac9c..28992c90bb 100644 --- a/api-js/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js +++ b/api/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js @@ -4,7 +4,7 @@ import { guidanceTagConnection } from '../index' describe('given the guidance Tag Connection connection object', () => { describe('testing its field definitions', () => { it('has a totalCount field', () => { - const demoType = guidanceTagConnection.connectionType.getFields() + const demoType = guidanceTagConnection.getFields() expect(demoType).toHaveProperty('totalCount') expect(demoType.totalCount.type).toMatchObject(GraphQLInt) @@ -13,7 +13,7 @@ describe('given the guidance Tag Connection connection object', () => { describe('testing its field resolvers', () => { describe('testing the totalCount resolver', () => { it('returns the resolved value', () => { - const demoType = guidanceTagConnection.connectionType.getFields() + const demoType = guidanceTagConnection.getFields() expect(demoType.totalCount.resolve({ totalCount: 1 })).toEqual(1) }) diff --git a/api-js/src/guidance-tag/objects/__tests__/guidance-tag.test.js b/api/src/guidance-tag/objects/__tests__/guidance-tag.test.js similarity index 88% rename from api-js/src/guidance-tag/objects/__tests__/guidance-tag.test.js rename to api/src/guidance-tag/objects/__tests__/guidance-tag.test.js index 0b5ee05714..3745705410 100644 --- a/api-js/src/guidance-tag/objects/__tests__/guidance-tag.test.js +++ b/api/src/guidance-tag/objects/__tests__/guidance-tag.test.js @@ -9,7 +9,7 @@ describe('given the guidanceTag gql object', () => { const demoType = guidanceTagType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a tagId field', () => { const demoType = guidanceTagType.getFields() @@ -33,15 +33,13 @@ describe('given the guidanceTag gql object', () => { const demoType = guidanceTagType.getFields() expect(demoType).toHaveProperty('refLinks') - expect(demoType.refLinks.type).toMatchObject(GraphQLList(refLinksType)) + expect(demoType.refLinks.type).toMatchObject(new GraphQLList(refLinksType)) }) it('has a refLinksTechnical field', () => { const demoType = guidanceTagType.getFields() expect(demoType).toHaveProperty('refLinksTech') - expect(demoType.refLinksTech.type).toMatchObject( - GraphQLList(refLinksType), - ) + expect(demoType.refLinksTech.type).toMatchObject(new GraphQLList(refLinksType)) }) }) describe('testing the field resolvers', () => { @@ -49,9 +47,7 @@ describe('given the guidanceTag gql object', () => { it('returns the resolved value', () => { const demoType = guidanceTagType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('guidanceTag', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('guidanceTag', 1)) }) }) describe('testing the tagId resolver', () => { @@ -65,18 +61,14 @@ describe('given the guidanceTag gql object', () => { it('returns the resolved value', () => { const demoType = guidanceTagType.getFields() - expect(demoType.tagName.resolve({ tagName: 'tagName' })).toEqual( - 'tagName', - ) + expect(demoType.tagName.resolve({ tagName: 'tagName' })).toEqual('tagName') }) }) describe('testing the guidance resolver', () => { it('returns the resolved value', () => { const demoType = guidanceTagType.getFields() - expect(demoType.guidance.resolve({ guidance: 'guidance' })).toEqual( - 'guidance', - ) + expect(demoType.guidance.resolve({ guidance: 'guidance' })).toEqual('guidance') }) }) describe('testing the refLinks resolver', () => { diff --git a/api-js/src/guidance-tag/objects/__tests__/ref-links.test.js b/api/src/guidance-tag/objects/__tests__/ref-links.test.js similarity index 81% rename from api-js/src/guidance-tag/objects/__tests__/ref-links.test.js rename to api/src/guidance-tag/objects/__tests__/ref-links.test.js index a46acb95f8..1de67ad91f 100644 --- a/api-js/src/guidance-tag/objects/__tests__/ref-links.test.js +++ b/api/src/guidance-tag/objects/__tests__/ref-links.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { refLinksType } from '../index' +import {refLinksType} from '../index' describe('given the refLinksType gql object', () => { describe('testing its field definitions', () => { @@ -23,7 +23,7 @@ describe('given the refLinksType gql object', () => { const demoType = refLinksType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) @@ -31,7 +31,7 @@ describe('given the refLinksType gql object', () => { it('returns the resolved value', () => { const demoType = refLinksType.getFields() - expect(demoType.refLink.resolve({ ref_link: 'ref_link' })).toEqual( + expect(demoType.refLink.resolve({ref_link: 'ref_link'})).toEqual( 'ref_link', ) }) diff --git a/api/src/guidance-tag/objects/guidance-tag-connection.js b/api/src/guidance-tag/objects/guidance-tag-connection.js new file mode 100644 index 0000000000..a6b8c50e2c --- /dev/null +++ b/api/src/guidance-tag/objects/guidance-tag-connection.js @@ -0,0 +1,18 @@ +import { GraphQLInt, GraphQLObjectType, GraphQLList } from 'graphql' +import { guidanceTagType } from './guidance-tag' + +export const guidanceTagConnection = new GraphQLObjectType({ + name: 'GuidanceTagConnection', + fields: () => ({ + guidanceTags: { + type: new GraphQLList(guidanceTagType), + description: '', + resolve: ({ guidanceTags }) => guidanceTags, + }, + totalCount: { + type: GraphQLInt, + description: 'The total amount of guidance tags for a given scan type.', + resolve: ({ totalCount }) => totalCount, + }, + }), +}) diff --git a/api/src/guidance-tag/objects/guidance-tag.js b/api/src/guidance-tag/objects/guidance-tag.js new file mode 100644 index 0000000000..687c5073da --- /dev/null +++ b/api/src/guidance-tag/objects/guidance-tag.js @@ -0,0 +1,44 @@ +import { GraphQLList, GraphQLObjectType, GraphQLString, GraphQLInt } from 'graphql' +import { globalIdField } from 'graphql-relay' + +import { refLinksType } from './ref-links' +import { nodeInterface } from '../../node' + +export const guidanceTagType = new GraphQLObjectType({ + name: 'GuidanceTag', + description: 'Details for a given guidance tag based on https://github.com/canada-ca/tracker/wiki/Guidance-Tags', + fields: () => ({ + id: globalIdField('guidanceTag'), + tagId: { + type: GraphQLString, + description: 'The guidance tag ID.', + resolve: ({ tagId }) => tagId, + }, + tagName: { + type: GraphQLString, + description: 'The guidance tag name.', + resolve: ({ tagName }) => tagName, + }, + guidance: { + type: GraphQLString, + description: 'Guidance for changes to record, or to maintain current stance.', + resolve: ({ guidance }) => guidance, + }, + refLinks: { + type: new GraphQLList(refLinksType), + description: 'Links to implementation guidance for a given tag.', + resolve: ({ refLinksGuide }) => refLinksGuide, + }, + refLinksTech: { + type: new GraphQLList(refLinksType), + description: 'Links to technical information for a given tag.', + resolve: ({ refLinksTechnical }) => refLinksTechnical, + }, + count: { + type: GraphQLInt, + description: 'Number of times the tag has been applied.', + resolve: ({ count }) => count, + }, + }), + interfaces: [nodeInterface], +}) diff --git a/api-js/src/guidance-tag/objects/index.js b/api/src/guidance-tag/objects/index.js similarity index 100% rename from api-js/src/guidance-tag/objects/index.js rename to api/src/guidance-tag/objects/index.js diff --git a/api/src/guidance-tag/objects/ref-links.js b/api/src/guidance-tag/objects/ref-links.js new file mode 100644 index 0000000000..e2c4644657 --- /dev/null +++ b/api/src/guidance-tag/objects/ref-links.js @@ -0,0 +1,19 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' + +export const refLinksType = new GraphQLObjectType({ + name: 'RefLinks', + description: + 'Object containing the information of various links for guidance or technical documentation.', + fields: () => ({ + description: { + type: GraphQLString, + description: 'Title of the guidance link.', + resolve: ({description}) => description, + }, + refLink: { + type: GraphQLString, + description: 'URL for the guidance documentation.', + resolve: ({ref_link: refLink}) => refLink, + }, + }), +}) diff --git a/api/src/initialize-loaders.js b/api/src/initialize-loaders.js new file mode 100644 index 0000000000..1ab26ba52e --- /dev/null +++ b/api/src/initialize-loaders.js @@ -0,0 +1,197 @@ +import { + loadAffiliationByKey, + loadAffiliationConnectionsByUserId, + loadAffiliationConnectionsByOrgId, +} from './affiliation/loaders' +import { + loadDkimFailConnectionsBySumId, + loadDmarcFailConnectionsBySumId, + loadDmarcSummaryConnectionsByUserId, + loadDmarcSummaryEdgeByDomainIdAndPeriod, + loadDmarcSummaryByKey, + loadFullPassConnectionsBySumId, + loadSpfFailureConnectionsBySumId, + loadStartDateFromPeriod, + loadDmarcYearlySumEdge, + loadAllVerifiedRuaDomains, +} from './dmarc-summaries/loaders' +import { + loadDomainByKey, + loadDomainByDomain, + loadDomainConnectionsByOrgId, + loadDomainConnectionsByUserId, + loadDkimSelectorsByDomainId, +} from './domain/loaders' +import { loadOrgByKey, loadOrganizationNamesById } from './organization/loaders' +import { loadMyTrackerByUserId, loadUserByUserName, loadUserByKey, loadUserConnectionsByUserId } from './user/loaders' +import { + loadVerifiedDomainsById, + loadVerifiedDomainByKey, + loadVerifiedDomainConnections, + loadVerifiedDomainConnectionsByOrgId, +} from './verified-domains/loaders' +import { + loadVerifiedOrgByKey, + loadVerifiedOrgBySlug, + loadVerifiedOrgConnectionsByDomainId, + loadVerifiedOrgConnections, +} from './verified-organizations/loaders' +import { loadTagByTagId, loadTagsByOrg } from './tags' + +export function initializeLoaders({ query, userKey, i18n, language, cleanseInput, loginRequiredBool, moment }) { + return { + loadTagByTagId: loadTagByTagId({ + query, + userKey, + i18n, + language, + }), + loadTagsByOrg: loadTagsByOrg({ + query, + userKey, + i18n, + language, + }), + loadDkimFailConnectionsBySumId: loadDkimFailConnectionsBySumId({ + query, + userKey, + cleanseInput, + i18n, + }), + loadDmarcFailConnectionsBySumId: loadDmarcFailConnectionsBySumId({ + query, + userKey, + cleanseInput, + i18n, + }), + loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId({ + query, + userKey, + cleanseInput, + i18n, + auth: { loginRequiredBool }, + loadStartDateFromPeriod: loadStartDateFromPeriod({ + moment, + userKey, + i18n, + }), + }), + loadDmarcSummaryEdgeByDomainIdAndPeriod: loadDmarcSummaryEdgeByDomainIdAndPeriod({ + query, + userKey, + i18n, + }), + loadDmarcSummaryByKey: loadDmarcSummaryByKey({ query, userKey, i18n }), + loadFullPassConnectionsBySumId: loadFullPassConnectionsBySumId({ + query, + userKey, + cleanseInput, + i18n, + }), + loadSpfFailureConnectionsBySumId: loadSpfFailureConnectionsBySumId({ + query, + userKey, + cleanseInput, + i18n, + }), + loadStartDateFromPeriod: loadStartDateFromPeriod({ + moment, + userKey, + i18n, + }), + loadDmarcYearlySumEdge: loadDmarcYearlySumEdge({ query, userKey, i18n }), + loadDomainByDomain: loadDomainByDomain({ query, userKey, i18n }), + loadDomainByKey: loadDomainByKey({ query, userKey, i18n }), + loadDomainConnectionsByOrgId: loadDomainConnectionsByOrgId({ + query, + userKey, + language, + cleanseInput, + i18n, + auth: { loginRequiredBool }, + }), + loadDomainConnectionsByUserId: loadDomainConnectionsByUserId({ + query, + userKey, + cleanseInput, + i18n, + auth: { loginRequiredBool }, + }), + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey, + cleanseInput, + i18n, + auth: { loginRequiredBool }, + }), + loadOrgByKey: loadOrgByKey({ query, language, userKey, i18n }), + loadOrganizationNamesById: loadOrganizationNamesById({ query, userKey, i18n }), + loadMyTrackerByUserId: loadMyTrackerByUserId({ + query, + language, + userKey, + i18n, + }), + loadUserByUserName: loadUserByUserName({ query, userKey, i18n }), + loadUserConnectionsByUserId: loadUserConnectionsByUserId({ + query, + userKey, + cleanseInput, + i18n, + }), + loadUserByKey: loadUserByKey({ query, userKey, i18n }), + loadAffiliationByKey: loadAffiliationByKey({ query, userKey, i18n }), + loadAffiliationConnectionsByUserId: loadAffiliationConnectionsByUserId({ + query, + language, + userKey, + cleanseInput, + i18n, + }), + loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId({ + query, + userKey, + cleanseInput, + i18n, + }), + loadVerifiedDomainsById: loadVerifiedDomainsById({ query, i18n }), + loadVerifiedDomainByKey: loadVerifiedDomainByKey({ query, i18n }), + loadVerifiedDomainConnections: loadVerifiedDomainConnections({ + query, + cleanseInput, + i18n, + }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), + loadVerifiedOrgByKey: loadVerifiedOrgByKey({ + query, + language, + i18n, + }), + loadVerifiedOrgBySlug: loadVerifiedOrgBySlug({ + query, + language, + i18n, + }), + loadVerifiedOrgConnectionsByDomainId: loadVerifiedOrgConnectionsByDomainId({ + query, + language, + cleanseInput, + i18n, + }), + loadVerifiedOrgConnections: loadVerifiedOrgConnections({ + query, + language, + cleanseInput, + i18n, + }), + loadAllVerifiedRuaDomains: loadAllVerifiedRuaDomains({ + query, + userKey, + i18n, + }), + } +} diff --git a/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-key.js.json b/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-key.js.json new file mode 100644 index 0000000000..bb0d83a43e --- /dev/null +++ b/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find user affiliation(s). Please try again.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-key.js", + 21 + ], + [ + "src/affiliation/loaders/load-affiliations-by-key.js", + 35 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-org-id.js.json b/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-org-id.js.json new file mode 100644 index 0000000000..a17d8d3caa --- /dev/null +++ b/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-org-id.js.json @@ -0,0 +1,58 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `affiliation`.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 33 + ] + ] + }, + "Passing both `first` and `last` to paginate the `affiliation` is not supported.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 42 + ] + ] + }, + "`{argSet}` on the `affiliations` cannot be less than zero.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 54 + ] + ] + }, + "Requesting `{amount}` records on the `affiliations` exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 65 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 80 + ] + ] + }, + "Unable to query affiliations. Please try again.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 136 + ] + ] + }, + "Unable to load affiliations. Please try again.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-org-id.js", + 146 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-user-id.js.json b/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-user-id.js.json new file mode 100644 index 0000000000..aa93700030 --- /dev/null +++ b/api/src/locale/_build/src/affiliation/loaders/load-affiliations-by-user-id.js.json @@ -0,0 +1,58 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `affiliation`.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 33 + ] + ] + }, + "Passing both `first` and `last` to paginate the `affiliation` is not supported.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 42 + ] + ] + }, + "`{argSet}` on the `affiliations` cannot be less than zero.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 54 + ] + ] + }, + "Requesting `{amount}` records on the `affiliations` exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 65 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 80 + ] + ] + }, + "Unable to query affiliations. Please try again.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 136 + ] + ] + }, + "Unable to load affiliations. Please try again.": { + "origin": [ + [ + "src/affiliation/loaders/load-affiliations-by-user-id.js", + 146 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/affiliation/mutations/invite-user-to-org.js.json b/api/src/locale/_build/src/affiliation/mutations/invite-user-to-org.js.json new file mode 100644 index 0000000000..e61dd989ef --- /dev/null +++ b/api/src/locale/_build/src/affiliation/mutations/invite-user-to-org.js.json @@ -0,0 +1,46 @@ +{ + "Unable to invite yourself to an org. Please try again.": { + "origin": [ + [ + "src/affiliation/mutations/invite-user-to-org.js", + 68 + ] + ] + }, + "Unable to invite user. Please try again.": { + "origin": [ + [ + "src/affiliation/mutations/invite-user-to-org.js", + 79 + ], + [ + "src/affiliation/mutations/invite-user-to-org.js", + 92 + ], + [ + "src/affiliation/mutations/invite-user-to-org.js", + 155 + ], + [ + "src/affiliation/mutations/invite-user-to-org.js", + 177 + ] + ] + }, + "Successfully sent invitation to service, and organization email.": { + "origin": [ + [ + "src/affiliation/mutations/invite-user-to-org.js", + 127 + ] + ] + }, + "Successfully invited user to organization, and sent notification email.": { + "origin": [ + [ + "src/affiliation/mutations/invite-user-to-org.js", + 186 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/affiliation/mutations/remove-user-from-org.js.json b/api/src/locale/_build/src/affiliation/mutations/remove-user-from-org.js.json new file mode 100644 index 0000000000..005bfcb82f --- /dev/null +++ b/api/src/locale/_build/src/affiliation/mutations/remove-user-from-org.js.json @@ -0,0 +1,46 @@ +{ + "Unable to remove user from organization. Please try again.": { + "origin": [ + [ + "src/affiliation/mutations/remove-user-from-org.js", + 52 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 63 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 74 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 91 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 100 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 143 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 154 + ], + [ + "src/affiliation/mutations/remove-user-from-org.js", + 170 + ] + ] + }, + "Successfully removed user from organization.": { + "origin": [ + [ + "src/affiliation/mutations/remove-user-from-org.js", + 163 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/affiliation/mutations/update-user-role.js.json b/api/src/locale/_build/src/affiliation/mutations/update-user-role.js.json new file mode 100644 index 0000000000..03a4930256 --- /dev/null +++ b/api/src/locale/_build/src/affiliation/mutations/update-user-role.js.json @@ -0,0 +1,66 @@ +{ + "Unable to update your own role. Please try again.": { + "origin": [ + [ + "src/affiliation/mutations/update-user-role.js", + 64 + ] + ] + }, + "Unable to update users role. Please try again.": { + "origin": [ + [ + "src/affiliation/mutations/update-user-role.js", + 75 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 85 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 95 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 110 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 153 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 172 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 185 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 201 + ], + [ + "src/affiliation/mutations/update-user-role.js", + 210 + ] + ] + }, + "Unable to update users role. Please invite user to the organization.": { + "origin": [ + [ + "src/affiliation/mutations/update-user-role.js", + 119 + ] + ] + }, + "User role was updated successfully.": { + "origin": [ + [ + "src/affiliation/mutations/update-user-role.js", + 218 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/auth/check-domain-ownership.js.json b/api/src/locale/_build/src/auth/check-domain-ownership.js.json new file mode 100644 index 0000000000..741f4a0b6c --- /dev/null +++ b/api/src/locale/_build/src/auth/check-domain-ownership.js.json @@ -0,0 +1,14 @@ +{ + "Error when retrieving dmarc report information. Please try again.": { + "origin": [ + [ + "src/auth/check-domain-ownership.js", + 22 + ], + [ + "src/auth/check-domain-ownership.js", + 35 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/auth/check-domain-permission.js.json b/api/src/locale/_build/src/auth/check-domain-permission.js.json new file mode 100644 index 0000000000..898db99b21 --- /dev/null +++ b/api/src/locale/_build/src/auth/check-domain-permission.js.json @@ -0,0 +1,18 @@ +{ + "Permission check error. Unable to request domain information.": { + "origin": [ + [ + "src/auth/check-domain-permission.js", + 22 + ], + [ + "src/auth/check-domain-permission.js", + 43 + ], + [ + "src/auth/check-domain-permission.js", + 54 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/auth/check-permission.js.json b/api/src/locale/_build/src/auth/check-permission.js.json new file mode 100644 index 0000000000..1d6e4ce841 --- /dev/null +++ b/api/src/locale/_build/src/auth/check-permission.js.json @@ -0,0 +1,26 @@ +{ + "Authentication error. Please sign in again.": { + "origin": [ + [ + "src/auth/check-permission.js", + 19 + ], + [ + "src/auth/check-permission.js", + 46 + ] + ] + }, + "Unable to check permission. Please try again.": { + "origin": [ + [ + "src/auth/check-permission.js", + 29 + ], + [ + "src/auth/check-permission.js", + 55 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/auth/check-user-is-admin-for-user.js.json b/api/src/locale/_build/src/auth/check-user-is-admin-for-user.js.json new file mode 100644 index 0000000000..995420638a --- /dev/null +++ b/api/src/locale/_build/src/auth/check-user-is-admin-for-user.js.json @@ -0,0 +1,22 @@ +{ + "Permission error, not an admin for this user.": { + "origin": [ + [ + "src/auth/check-user-is-admin-for-user.js", + 19 + ], + [ + "src/auth/check-user-is-admin-for-user.js", + 29 + ], + [ + "src/auth/check-user-is-admin-for-user.js", + 60 + ], + [ + "src/auth/check-user-is-admin-for-user.js", + 70 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/auth/user-required.js.json b/api/src/locale/_build/src/auth/user-required.js.json new file mode 100644 index 0000000000..c8a80743a8 --- /dev/null +++ b/api/src/locale/_build/src/auth/user-required.js.json @@ -0,0 +1,18 @@ +{ + "Authentication error. Please sign in.": { + "origin": [ + [ + "src/auth/user-required.js", + 12 + ], + [ + "src/auth/user-required.js", + 23 + ], + [ + "src/auth/user-required.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/auth/verify-jwt.js.json b/api/src/locale/_build/src/auth/verify-jwt.js.json new file mode 100644 index 0000000000..ee9b99d52a --- /dev/null +++ b/api/src/locale/_build/src/auth/verify-jwt.js.json @@ -0,0 +1,10 @@ +{ + "Invalid token, please request a new one.": { + "origin": [ + [ + "src/auth/verify-jwt.js", + 15 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/dmarc-report/loaders/load-dmarc-report.js.json b/api/src/locale/_build/src/dmarc-report/loaders/load-dmarc-report.js.json new file mode 100644 index 0000000000..6c54363a52 --- /dev/null +++ b/api/src/locale/_build/src/dmarc-report/loaders/load-dmarc-report.js.json @@ -0,0 +1,10 @@ +{ + "Unable to retrieve {0} for domain: {domain}.": { + "origin": [ + [ + "src/dmarc-report/loaders/load-dmarc-report.js", + 39 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/loaders/load-domain-by-domain.js.json b/api/src/locale/_build/src/domain/loaders/load-domain-by-domain.js.json new file mode 100644 index 0000000000..3f5e4bc0d3 --- /dev/null +++ b/api/src/locale/_build/src/domain/loaders/load-domain-by-domain.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find domain. Please try again.": { + "origin": [ + [ + "src/domain/loaders/load-domain-by-domain.js", + 18 + ], + [ + "src/domain/loaders/load-domain-by-domain.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/loaders/load-domain-by-key.js.json b/api/src/locale/_build/src/domain/loaders/load-domain-by-key.js.json new file mode 100644 index 0000000000..d5a007c885 --- /dev/null +++ b/api/src/locale/_build/src/domain/loaders/load-domain-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find domain. Please try again.": { + "origin": [ + [ + "src/domain/loaders/load-domain-by-key.js", + 18 + ], + [ + "src/domain/loaders/load-domain-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/loaders/load-domain-connections-by-organizations-id.js.json b/api/src/locale/_build/src/domain/loaders/load-domain-connections-by-organizations-id.js.json new file mode 100644 index 0000000000..ed7ff41f51 --- /dev/null +++ b/api/src/locale/_build/src/domain/loaders/load-domain-connections-by-organizations-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `domain` connection.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 42 + ] + ] + }, + "Passing both `first` and `last` to paginate the `domain` connection is not supported.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 51 + ] + ] + }, + "`{argSet}` on the `domain` connection cannot be less than zero.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 63 + ] + ] + }, + "Requesting `{amount}` records on the `domain` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 74 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 89 + ] + ] + }, + "Unable to load domains. Please try again.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 151 + ], + [ + "src/domain/loaders/load-domain-connections-by-organizations-id.js", + 161 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/loaders/load-domain-connections-by-user-id.js.json b/api/src/locale/_build/src/domain/loaders/load-domain-connections-by-user-id.js.json new file mode 100644 index 0000000000..dae69e5026 --- /dev/null +++ b/api/src/locale/_build/src/domain/loaders/load-domain-connections-by-user-id.js.json @@ -0,0 +1,58 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `domain` connection.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 40 + ] + ] + }, + "Passing both `first` and `last` to paginate the `domain` connection is not supported.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 49 + ] + ] + }, + "`{argSet}` on the `domain` connection cannot be less than zero.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 61 + ] + ] + }, + "Requesting `{amount}` records on the `domain` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 72 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 87 + ] + ] + }, + "Unable to query domains. Please try again.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 147 + ] + ] + }, + "Unable to load domains. Please try again.": { + "origin": [ + [ + "src/domain/loaders/load-domain-connections-by-user-id.js", + 157 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/mutations/create-domain.js.json b/api/src/locale/_build/src/domain/mutations/create-domain.js.json new file mode 100644 index 0000000000..31010af368 --- /dev/null +++ b/api/src/locale/_build/src/domain/mutations/create-domain.js.json @@ -0,0 +1,26 @@ +{ + "Unable to create domain. Please try again.": { + "origin": [ + [ + "src/domain/mutations/create-domain.js", + 69 + ], + [ + "src/domain/mutations/create-domain.js", + 83 + ], + [ + "src/domain/mutations/create-domain.js", + 108 + ], + [ + "src/domain/mutations/create-domain.js", + 117 + ], + [ + "src/domain/mutations/create-domain.js", + 168 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/mutations/remove-domain.js.json b/api/src/locale/_build/src/domain/mutations/remove-domain.js.json new file mode 100644 index 0000000000..5bc09f916c --- /dev/null +++ b/api/src/locale/_build/src/domain/mutations/remove-domain.js.json @@ -0,0 +1,50 @@ +{ + "Unable to remove domain. Please try again.": { + "origin": [ + [ + "src/domain/mutations/remove-domain.js", + 58 + ], + [ + "src/domain/mutations/remove-domain.js", + 69 + ], + [ + "src/domain/mutations/remove-domain.js", + 80 + ], + [ + "src/domain/mutations/remove-domain.js", + 87 + ], + [ + "src/domain/mutations/remove-domain.js", + 100 + ], + [ + "src/domain/mutations/remove-domain.js", + 171 + ], + [ + "src/domain/mutations/remove-domain.js", + 188 + ], + [ + "src/domain/mutations/remove-domain.js", + 209 + ], + [ + "src/domain/mutations/remove-domain.js", + 220 + ] + ] + }, + "Successfully removed domain: {0} from {1}.": { + "origin": [ + [ + "src/domain/mutations/remove-domain.js", + 228 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/mutations/request-scan.js.json b/api/src/locale/_build/src/domain/mutations/request-scan.js.json new file mode 100644 index 0000000000..1c29375f69 --- /dev/null +++ b/api/src/locale/_build/src/domain/mutations/request-scan.js.json @@ -0,0 +1,38 @@ +{ + "Unable to request a on time scan on this domain.": { + "origin": [ + [ + "src/domain/mutations/request-scan.js", + 61 + ], + [ + "src/domain/mutations/request-scan.js", + 73 + ] + ] + }, + "Unable to dispatch one time scan. Please try again.": { + "origin": [ + [ + "src/domain/mutations/request-scan.js", + 95 + ], + [ + "src/domain/mutations/request-scan.js", + 109 + ], + [ + "src/domain/mutations/request-scan.js", + 123 + ] + ] + }, + "Successfully dispatched one time scan.": { + "origin": [ + [ + "src/domain/mutations/request-scan.js", + 132 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/mutations/update-domain.js.json b/api/src/locale/_build/src/domain/mutations/update-domain.js.json new file mode 100644 index 0000000000..d084cac7ae --- /dev/null +++ b/api/src/locale/_build/src/domain/mutations/update-domain.js.json @@ -0,0 +1,34 @@ +{ + "Unable to update domain. Please try again.": { + "origin": [ + [ + "src/domain/mutations/update-domain.js", + 74 + ], + [ + "src/domain/mutations/update-domain.js", + 84 + ], + [ + "src/domain/mutations/update-domain.js", + 98 + ], + [ + "src/domain/mutations/update-domain.js", + 113 + ], + [ + "src/domain/mutations/update-domain.js", + 120 + ], + [ + "src/domain/mutations/update-domain.js", + 153 + ], + [ + "src/domain/mutations/update-domain.js", + 163 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/queries/find-domain-by-domain.js.json b/api/src/locale/_build/src/domain/queries/find-domain-by-domain.js.json new file mode 100644 index 0000000000..657c8817db --- /dev/null +++ b/api/src/locale/_build/src/domain/queries/find-domain-by-domain.js.json @@ -0,0 +1,18 @@ +{ + "No domain with the provided domain could be found.": { + "origin": [ + [ + "src/domain/queries/find-domain-by-domain.js", + 38 + ] + ] + }, + "Could not retrieve specified domain.": { + "origin": [ + [ + "src/domain/queries/find-domain-by-domain.js", + 47 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/domain/queries/find-my-domains.js.json b/api/src/locale/_build/src/domain/queries/find-my-domains.js.json new file mode 100644 index 0000000000..21599fa3e9 --- /dev/null +++ b/api/src/locale/_build/src/domain/queries/find-my-domains.js.json @@ -0,0 +1,10 @@ +{ + "Unable to load domains. Please try again.": { + "origin": [ + [ + "src/domain/queries/find-my-domains.js", + 38 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-dkim-by-key.js.json b/api/src/locale/_build/src/email-scan/loaders/load-dkim-by-key.js.json new file mode 100644 index 0000000000..b6e0368dab --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-dkim-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find dkim scan. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-by-key.js", + 18 + ], + [ + "src/email-scan/loaders/load-dkim-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-dkim-connections-by-domain-id.js.json b/api/src/locale/_build/src/email-scan/loaders/load-dkim-connections-by-domain-id.js.json new file mode 100644 index 0000000000..a5b4d4dd6c --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-dkim-connections-by-domain-id.js.json @@ -0,0 +1,58 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `dkim` connection.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 40 + ] + ] + }, + "Passing both `first` and `last` to paginate the `dkim` connection is not supported.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 49 + ], + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 85 + ] + ] + }, + "`{argSet}` on the `dkim` connection cannot be less than zero.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 61 + ] + ] + }, + "Requesting {amount} records on the `dkim` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 72 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 96 + ] + ] + }, + "Unable to load dkim scans. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 152 + ], + [ + "src/email-scan/loaders/load-dkim-connections-by-domain-id.js", + 162 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-dkim-result-by-key.js.json b/api/src/locale/_build/src/email-scan/loaders/load-dkim-result-by-key.js.json new file mode 100644 index 0000000000..4ad657e0f7 --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-dkim-result-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find dkim result. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-result-by-key.js", + 18 + ], + [ + "src/email-scan/loaders/load-dkim-result-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js.json b/api/src/locale/_build/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js.json new file mode 100644 index 0000000000..50558e4692 --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `dkimResults` connection.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `dkimResults` connection is not supported.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 39 + ] + ] + }, + "`{argSet}` on the `dkimResults` connection cannot be less than zero.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 51 + ] + ] + }, + "Requesting {amount} records on the `dkimResults` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 77 + ] + ] + }, + "Unable to load dkim results. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 131 + ], + [ + "src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js", + 141 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-dmarc-by-key.js.json b/api/src/locale/_build/src/email-scan/loaders/load-dmarc-by-key.js.json new file mode 100644 index 0000000000..556374a9ac --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-dmarc-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find dmarc scan. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-by-key.js", + 18 + ], + [ + "src/email-scan/loaders/load-dmarc-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js.json b/api/src/locale/_build/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js.json new file mode 100644 index 0000000000..f77bfc6a57 --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `dmarc` connection.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 40 + ] + ] + }, + "Passing both `first` and `last` to paginate the `dmarc` connection is not supported.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 49 + ] + ] + }, + "`{argSet}` on the `dmarc` connection cannot be less than zero.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 62 + ] + ] + }, + "Requesting {amount} records on the `dmarc` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 73 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 88 + ] + ] + }, + "Unable to load dmarc scans. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 145 + ], + [ + "src/email-scan/loaders/load-dmarc-connections-by-domain-id.js", + 155 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-spf-by-key.js.json b/api/src/locale/_build/src/email-scan/loaders/load-spf-by-key.js.json new file mode 100644 index 0000000000..e79d28d864 --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-spf-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find spf scan. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-by-key.js", + 18 + ], + [ + "src/email-scan/loaders/load-spf-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/email-scan/loaders/load-spf-connections-by-domain-id.js.json b/api/src/locale/_build/src/email-scan/loaders/load-spf-connections-by-domain-id.js.json new file mode 100644 index 0000000000..dceec2cac1 --- /dev/null +++ b/api/src/locale/_build/src/email-scan/loaders/load-spf-connections-by-domain-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `spf` connection.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 40 + ] + ] + }, + "Passing both `first` and `last` to paginate the `spf` connection is not supported.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 49 + ] + ] + }, + "`{argSet}` on the `spf` connection cannot be less than zero.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 61 + ] + ] + }, + "Requesting {amount} records on the `spf` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 72 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 87 + ] + ] + }, + "Unable to load spf scans. Please try again.": { + "origin": [ + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 143 + ], + [ + "src/email-scan/loaders/load-spf-connections-by-domain-id.js", + 153 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js.json new file mode 100644 index 0000000000..dec5eaf2cc --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `guidanceTag` connection.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `guidanceTag` connection is not supported.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 39 + ] + ] + }, + "`{argSet}` on the `guidanceTag` connection cannot be less than zero.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 51 + ] + ] + }, + "Requesting `{amount}` records on the `guidanceTag` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 77 + ] + ] + }, + "Unable to load dkim guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 130 + ], + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js", + 142 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-dkim-guidance-tags.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-dkim-guidance-tags.js.json new file mode 100644 index 0000000000..c3e8d8ce5c --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-dkim-guidance-tags.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find dkim guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags.js", + 18 + ], + [ + "src/guidance-tag/loaders/load-dkim-guidance-tags.js", + 32 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js.json new file mode 100644 index 0000000000..4d7b662054 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `guidanceTag` connection.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `guidanceTag` connection is not supported.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 39 + ] + ] + }, + "`{argSet}` on the `guidanceTag` connection cannot be less than zero.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 51 + ] + ] + }, + "Requesting `{amount}` records on the `guidanceTag` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 77 + ] + ] + }, + "Unable to load dmarc guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 130 + ], + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js", + 142 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-dmarc-guidance-tags.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-dmarc-guidance-tags.js.json new file mode 100644 index 0000000000..d196f91247 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-dmarc-guidance-tags.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find dmarc guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags.js", + 18 + ], + [ + "src/guidance-tag/loaders/load-dmarc-guidance-tags.js", + 32 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-https-guidance-tags-connections.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-https-guidance-tags-connections.js.json new file mode 100644 index 0000000000..fba0542715 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-https-guidance-tags-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `guidanceTag` connection.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `guidanceTag` connection is not supported.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 39 + ] + ] + }, + "`{argSet}` on the `guidanceTag` connection cannot be less than zero.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 51 + ] + ] + }, + "Requesting `{amount}` records on the `guidanceTag` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 77 + ] + ] + }, + "Unable to load https guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 130 + ], + [ + "src/guidance-tag/loaders/load-https-guidance-tags-connections.js", + 142 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-https-guidance-tags.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-https-guidance-tags.js.json new file mode 100644 index 0000000000..3b7cb5f0e6 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-https-guidance-tags.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find https guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-https-guidance-tags.js", + 18 + ], + [ + "src/guidance-tag/loaders/load-https-guidance-tags.js", + 32 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js.json new file mode 100644 index 0000000000..7cbdb313f8 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `guidanceTag` connection.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `guidanceTag` connection is not supported.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 39 + ] + ] + }, + "`{argSet}` on the `guidanceTag` connection cannot be less than zero.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 51 + ] + ] + }, + "Requesting `{amount}` records on the `guidanceTag` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 77 + ] + ] + }, + "Unable to load spf guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 130 + ], + [ + "src/guidance-tag/loaders/load-spf-guidance-tags-connections.js", + 142 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-spf-guidance-tags.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-spf-guidance-tags.js.json new file mode 100644 index 0000000000..ec366a0158 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-spf-guidance-tags.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find spf guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-spf-guidance-tags.js", + 18 + ], + [ + "src/guidance-tag/loaders/load-spf-guidance-tags.js", + 32 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js.json new file mode 100644 index 0000000000..ecc829efe4 --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `guidanceTag` connection.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `guidanceTag` connection is not supported.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 39 + ] + ] + }, + "`{argSet}` on the `guidanceTag` connection cannot be less than zero.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 51 + ] + ] + }, + "Requesting `{amount}` records on the `guidanceTag` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 77 + ] + ] + }, + "Unable to load ssl guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 130 + ], + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js", + 142 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/guidance-tag/loaders/load-ssl-guidance-tags.js.json b/api/src/locale/_build/src/guidance-tag/loaders/load-ssl-guidance-tags.js.json new file mode 100644 index 0000000000..e6ea23e78f --- /dev/null +++ b/api/src/locale/_build/src/guidance-tag/loaders/load-ssl-guidance-tags.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find ssl guidance tags. Please try again.": { + "origin": [ + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags.js", + 18 + ], + [ + "src/guidance-tag/loaders/load-ssl-guidance-tags.js", + 32 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-authenticate-email.js.json b/api/src/locale/_build/src/notify/notify-send-authenticate-email.js.json new file mode 100644 index 0000000000..ae0389f802 --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-authenticate-email.js.json @@ -0,0 +1,10 @@ +{ + "Unable to authenticate. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-authenticate-email.js", + 16 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-authenticate-text-msg.js.json b/api/src/locale/_build/src/notify/notify-send-authenticate-text-msg.js.json new file mode 100644 index 0000000000..8c98a8e462 --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-authenticate-text-msg.js.json @@ -0,0 +1,10 @@ +{ + "Unable to authenticate. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-authenticate-text-msg.js", + 31 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-org-invite-create-account.js.json b/api/src/locale/_build/src/notify/notify-send-org-invite-create-account.js.json new file mode 100644 index 0000000000..5522efd12e --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-org-invite-create-account.js.json @@ -0,0 +1,10 @@ +{ + "Unable to send org invite email. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-org-invite-create-account.js", + 22 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-org-invite-email.js.json b/api/src/locale/_build/src/notify/notify-send-org-invite-email.js.json new file mode 100644 index 0000000000..f1a1f9fd6a --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-org-invite-email.js.json @@ -0,0 +1,10 @@ +{ + "Unable to send org invite email. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-org-invite-email.js", + 20 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-password-reset-email.js.json b/api/src/locale/_build/src/notify/notify-send-password-reset-email.js.json new file mode 100644 index 0000000000..1c639d84b2 --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-password-reset-email.js.json @@ -0,0 +1,10 @@ +{ + "Unable to send password reset email. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-password-reset-email.js", + 21 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-tfa-text-msg.js.json b/api/src/locale/_build/src/notify/notify-send-tfa-text-msg.js.json new file mode 100644 index 0000000000..dbd85c0265 --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-tfa-text-msg.js.json @@ -0,0 +1,10 @@ +{ + "Unable to send two factor authentication message. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-tfa-text-msg.js", + 21 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/notify/notify-send-verification-email.js.json b/api/src/locale/_build/src/notify/notify-send-verification-email.js.json new file mode 100644 index 0000000000..8e9a23785c --- /dev/null +++ b/api/src/locale/_build/src/notify/notify-send-verification-email.js.json @@ -0,0 +1,10 @@ +{ + "Unable to send verification email. Please try again.": { + "origin": [ + [ + "src/notify/notify-send-verification-email.js", + 21 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/loaders/load-organization-by-key.js.json b/api/src/locale/_build/src/organization/loaders/load-organization-by-key.js.json new file mode 100644 index 0000000000..ae8dc91814 --- /dev/null +++ b/api/src/locale/_build/src/organization/loaders/load-organization-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find organization. Please try again.": { + "origin": [ + [ + "src/organization/loaders/load-organization-by-key.js", + 19 + ], + [ + "src/organization/loaders/load-organization-by-key.js", + 31 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/loaders/load-organization-by-slug.js.json b/api/src/locale/_build/src/organization/loaders/load-organization-by-slug.js.json new file mode 100644 index 0000000000..0afc3d5564 --- /dev/null +++ b/api/src/locale/_build/src/organization/loaders/load-organization-by-slug.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find organization. Please try again.": { + "origin": [ + [ + "src/organization/loaders/load-organization-by-slug.js", + 19 + ], + [ + "src/organization/loaders/load-organization-by-slug.js", + 31 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/loaders/load-organization-connections-by-domain-id.js.json b/api/src/locale/_build/src/organization/loaders/load-organization-connections-by-domain-id.js.json new file mode 100644 index 0000000000..e6257503ba --- /dev/null +++ b/api/src/locale/_build/src/organization/loaders/load-organization-connections-by-domain-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `organization` connection.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 34 + ] + ] + }, + "Passing both `first` and `last` to paginate the `organization` connection is not supported.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 43 + ] + ] + }, + "`{argSet}` on the `organization` connection cannot be less than zero.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 55 + ] + ] + }, + "Requesting `{amount}` records on the `organization` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 66 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 81 + ] + ] + }, + "Unable to load organizations. Please try again.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 141 + ], + [ + "src/organization/loaders/load-organization-connections-by-domain-id.js", + 151 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/loaders/load-organization-connections-by-user-id.js.json b/api/src/locale/_build/src/organization/loaders/load-organization-connections-by-user-id.js.json new file mode 100644 index 0000000000..e89344b31d --- /dev/null +++ b/api/src/locale/_build/src/organization/loaders/load-organization-connections-by-user-id.js.json @@ -0,0 +1,58 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `organization` connection.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 34 + ] + ] + }, + "Passing both `first` and `last` to paginate the `organization` connection is not supported.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 43 + ] + ] + }, + "`{argSet}` on the `organization` connection cannot be less than zero.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 55 + ] + ] + }, + "Requesting `{amount}` records on the `organization` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 66 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 81 + ] + ] + }, + "Unable to query organizations. Please try again.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 136 + ] + ] + }, + "Unable to load organizations. Please try again.": { + "origin": [ + [ + "src/organization/loaders/load-organization-connections-by-user-id.js", + 146 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/mutations/create-organization.js.json b/api/src/locale/_build/src/organization/mutations/create-organization.js.json new file mode 100644 index 0000000000..cafa7ebf53 --- /dev/null +++ b/api/src/locale/_build/src/organization/mutations/create-organization.js.json @@ -0,0 +1,22 @@ +{ + "Unable to create organization. Please try again.": { + "origin": [ + [ + "src/organization/mutations/create-organization.js", + 134 + ], + [ + "src/organization/mutations/create-organization.js", + 188 + ], + [ + "src/organization/mutations/create-organization.js", + 209 + ], + [ + "src/organization/mutations/create-organization.js", + 220 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/mutations/remove-organization.js.json b/api/src/locale/_build/src/organization/mutations/remove-organization.js.json new file mode 100644 index 0000000000..88f0d9486c --- /dev/null +++ b/api/src/locale/_build/src/organization/mutations/remove-organization.js.json @@ -0,0 +1,38 @@ +{ + "Unable to remove organization. Please try again.": { + "origin": [ + [ + "src/organization/mutations/remove-organization.js", + 52 + ], + [ + "src/organization/mutations/remove-organization.js", + 65 + ], + [ + "src/organization/mutations/remove-organization.js", + 74 + ], + [ + "src/organization/mutations/remove-organization.js", + 145 + ], + [ + "src/organization/mutations/remove-organization.js", + 177 + ], + [ + "src/organization/mutations/remove-organization.js", + 188 + ] + ] + }, + "Successfully removed organization: {0}.": { + "origin": [ + [ + "src/organization/mutations/remove-organization.js", + 198 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/mutations/update-organization.js.json b/api/src/locale/_build/src/organization/mutations/update-organization.js.json new file mode 100644 index 0000000000..c63ff6cc4a --- /dev/null +++ b/api/src/locale/_build/src/organization/mutations/update-organization.js.json @@ -0,0 +1,26 @@ +{ + "Unable to update organization. Please try again.": { + "origin": [ + [ + "src/organization/mutations/update-organization.js", + 138 + ], + [ + "src/organization/mutations/update-organization.js", + 150 + ], + [ + "src/organization/mutations/update-organization.js", + 167 + ], + [ + "src/organization/mutations/update-organization.js", + 223 + ], + [ + "src/organization/mutations/update-organization.js", + 234 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/mutations/verify-organization.js.json b/api/src/locale/_build/src/organization/mutations/verify-organization.js.json new file mode 100644 index 0000000000..717d2636a1 --- /dev/null +++ b/api/src/locale/_build/src/organization/mutations/verify-organization.js.json @@ -0,0 +1,38 @@ +{ + "Unable to verify organization. Please try again.": { + "origin": [ + [ + "src/organization/mutations/verify-organization.js", + 47 + ], + [ + "src/organization/mutations/verify-organization.js", + 59 + ], + [ + "src/organization/mutations/verify-organization.js", + 99 + ], + [ + "src/organization/mutations/verify-organization.js", + 110 + ] + ] + }, + "Organization has already been verified.": { + "origin": [ + [ + "src/organization/mutations/verify-organization.js", + 68 + ] + ] + }, + "Successfully verified organization: {0}.": { + "origin": [ + [ + "src/organization/mutations/verify-organization.js", + 118 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/objects/organization-summary.js.json b/api/src/locale/_build/src/organization/objects/organization-summary.js.json new file mode 100644 index 0000000000..2ab8b1cf59 --- /dev/null +++ b/api/src/locale/_build/src/organization/objects/organization-summary.js.json @@ -0,0 +1,26 @@ +{ + "pass": { + "origin": [ + [ + "src/organization/objects/organization-summary.js", + 26 + ], + [ + "src/organization/objects/organization-summary.js", + 59 + ] + ] + }, + "fail": { + "origin": [ + [ + "src/organization/objects/organization-summary.js", + 31 + ], + [ + "src/organization/objects/organization-summary.js", + 64 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/objects/organization.js.json b/api/src/locale/_build/src/organization/objects/organization.js.json new file mode 100644 index 0000000000..30605b66bf --- /dev/null +++ b/api/src/locale/_build/src/organization/objects/organization.js.json @@ -0,0 +1,10 @@ +{ + "Cannot query affiliations on organization without admin permission or higher.": { + "origin": [ + [ + "src/organization/objects/organization.js", + 126 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/queries/find-my-organizations.js.json b/api/src/locale/_build/src/organization/queries/find-my-organizations.js.json new file mode 100644 index 0000000000..d2815639c2 --- /dev/null +++ b/api/src/locale/_build/src/organization/queries/find-my-organizations.js.json @@ -0,0 +1,10 @@ +{ + "Unable to load organizations. Please try again.": { + "origin": [ + [ + "src/organization/queries/find-my-organizations.js", + 32 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/organization/queries/find-organization-by-slug.js.json b/api/src/locale/_build/src/organization/queries/find-organization-by-slug.js.json new file mode 100644 index 0000000000..7a56e97d0b --- /dev/null +++ b/api/src/locale/_build/src/organization/queries/find-organization-by-slug.js.json @@ -0,0 +1,18 @@ +{ + "No organization with the provided slug could be found.": { + "origin": [ + [ + "src/organization/queries/find-organization-by-slug.js", + 40 + ] + ] + }, + "Could not retrieve specified organization.": { + "origin": [ + [ + "src/organization/queries/find-organization-by-slug.js", + 49 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/summaries/loaders/load-chart-summary-by-key.js.json b/api/src/locale/_build/src/summaries/loaders/load-chart-summary-by-key.js.json new file mode 100644 index 0000000000..24111425c4 --- /dev/null +++ b/api/src/locale/_build/src/summaries/loaders/load-chart-summary-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find summary. Please try again.": { + "origin": [ + [ + "src/summaries/loaders/load-chart-summary-by-key.js", + 18 + ], + [ + "src/summaries/loaders/load-chart-summary-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/summaries/queries/mail-summary.js.json b/api/src/locale/_build/src/summaries/queries/mail-summary.js.json new file mode 100644 index 0000000000..adba99e81c --- /dev/null +++ b/api/src/locale/_build/src/summaries/queries/mail-summary.js.json @@ -0,0 +1,26 @@ +{ + "Unable to load mail summary. Please try again.": { + "origin": [ + [ + "src/summaries/queries/mail-summary.js", + 12 + ] + ] + }, + "pass": { + "origin": [ + [ + "src/summaries/queries/mail-summary.js", + 17 + ] + ] + }, + "fail": { + "origin": [ + [ + "src/summaries/queries/mail-summary.js", + 22 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/summaries/queries/web-summary.js.json b/api/src/locale/_build/src/summaries/queries/web-summary.js.json new file mode 100644 index 0000000000..bcc2278b52 --- /dev/null +++ b/api/src/locale/_build/src/summaries/queries/web-summary.js.json @@ -0,0 +1,26 @@ +{ + "Unable to load web summary. Please try again.": { + "origin": [ + [ + "src/summaries/queries/web-summary.js", + 13 + ] + ] + }, + "pass": { + "origin": [ + [ + "src/summaries/queries/web-summary.js", + 18 + ] + ] + }, + "fail": { + "origin": [ + [ + "src/summaries/queries/web-summary.js", + 23 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/loaders/load-user-by-key.js.json b/api/src/locale/_build/src/user/loaders/load-user-by-key.js.json new file mode 100644 index 0000000000..4a8467b819 --- /dev/null +++ b/api/src/locale/_build/src/user/loaders/load-user-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find user. Please try again.": { + "origin": [ + [ + "src/user/loaders/load-user-by-key.js", + 18 + ], + [ + "src/user/loaders/load-user-by-key.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/loaders/load-user-by-username.js.json b/api/src/locale/_build/src/user/loaders/load-user-by-username.js.json new file mode 100644 index 0000000000..efbff91580 --- /dev/null +++ b/api/src/locale/_build/src/user/loaders/load-user-by-username.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find user. Please try again.": { + "origin": [ + [ + "src/user/loaders/load-user-by-username.js", + 18 + ], + [ + "src/user/loaders/load-user-by-username.js", + 30 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/authenticate.js.json b/api/src/locale/_build/src/user/mutations/authenticate.js.json new file mode 100644 index 0000000000..94ee8b61b9 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/authenticate.js.json @@ -0,0 +1,22 @@ +{ + "Unable to authenticate. Please try again.": { + "origin": [ + [ + "src/user/mutations/authenticate.js", + 56 + ], + [ + "src/user/mutations/authenticate.js", + 66 + ], + [ + "src/user/mutations/authenticate.js", + 83 + ], + [ + "src/user/mutations/authenticate.js", + 101 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/reset-password.js.json b/api/src/locale/_build/src/user/mutations/reset-password.js.json new file mode 100644 index 0000000000..b02bbab86c --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/reset-password.js.json @@ -0,0 +1,46 @@ +{ + "Unable to reset password. Please try again.": { + "origin": [ + [ + "src/user/mutations/reset-password.js", + 60 + ], + [ + "src/user/mutations/reset-password.js", + 70 + ], + [ + "src/user/mutations/reset-password.js", + 78 + ], + [ + "src/user/mutations/reset-password.js", + 111 + ] + ] + }, + "New passwords do not match. Please try again.": { + "origin": [ + [ + "src/user/mutations/reset-password.js", + 86 + ] + ] + }, + "Password is not strong enough. Please try again.": { + "origin": [ + [ + "src/user/mutations/reset-password.js", + 95 + ] + ] + }, + "Password was successfully reset.": { + "origin": [ + [ + "src/user/mutations/reset-password.js", + 117 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/send-email-verification.js.json b/api/src/locale/_build/src/user/mutations/send-email-verification.js.json new file mode 100644 index 0000000000..63560bc57f --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/send-email-verification.js.json @@ -0,0 +1,10 @@ +{ + "If an account with this username is found, an email verification link will be found in your inbox.": { + "origin": [ + [ + "src/user/mutations/send-email-verification.js", + 71 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/send-password-reset.js.json b/api/src/locale/_build/src/user/mutations/send-password-reset.js.json new file mode 100644 index 0000000000..079b141102 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/send-password-reset.js.json @@ -0,0 +1,10 @@ +{ + "If an account with this username is found, a password reset link will be found in your inbox.": { + "origin": [ + [ + "src/user/mutations/send-password-reset.js", + 72 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/send-phone-code.js.json b/api/src/locale/_build/src/user/mutations/send-phone-code.js.json new file mode 100644 index 0000000000..e31c865d06 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/send-phone-code.js.json @@ -0,0 +1,34 @@ +{ + "Authentication error, please sign in again.": { + "origin": [ + [ + "src/user/mutations/send-phone-code.js", + 48 + ] + ] + }, + "Unable to send TFA code, please try again.": { + "origin": [ + [ + "src/user/mutations/send-phone-code.js", + 58 + ], + [ + "src/user/mutations/send-phone-code.js", + 76 + ], + [ + "src/user/mutations/send-phone-code.js", + 106 + ] + ] + }, + "Two factor code has been successfully sent, you will receive a text message shortly.": { + "origin": [ + [ + "src/user/mutations/send-phone-code.js", + 125 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/sign-in.js.json b/api/src/locale/_build/src/user/mutations/sign-in.js.json new file mode 100644 index 0000000000..b745bef255 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/sign-in.js.json @@ -0,0 +1,34 @@ +{ + "Unable to sign in, please try again.": { + "origin": [ + [ + "src/user/mutations/sign-in.js", + 53 + ], + [ + "src/user/mutations/sign-in.js", + 84 + ], + [ + "src/user/mutations/sign-in.js", + 103 + ], + [ + "src/user/mutations/sign-in.js", + 152 + ], + [ + "src/user/mutations/sign-in.js", + 157 + ] + ] + }, + "Too many failed login attempts, please reset your password, and try again.": { + "origin": [ + [ + "src/user/mutations/sign-in.js", + 63 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/sign-up.js.json b/api/src/locale/_build/src/user/mutations/sign-up.js.json new file mode 100644 index 0000000000..8693567d87 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/sign-up.js.json @@ -0,0 +1,38 @@ +{ + "Password is too short.": { + "origin": [ + [ + "src/user/mutations/sign-up.js", + 71 + ] + ] + }, + "Passwords do not match.": { + "origin": [ + [ + "src/user/mutations/sign-up.js", + 79 + ] + ] + }, + "Username already in use.": { + "origin": [ + [ + "src/user/mutations/sign-up.js", + 89 + ] + ] + }, + "Unable to sign up. Please try again.": { + "origin": [ + [ + "src/user/mutations/sign-up.js", + 116 + ], + [ + "src/user/mutations/sign-up.js", + 125 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/update-user-password.js.json b/api/src/locale/_build/src/user/mutations/update-user-password.js.json new file mode 100644 index 0000000000..035fdfc879 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/update-user-password.js.json @@ -0,0 +1,54 @@ +{ + "Authentication error, please sign in again.": { + "origin": [ + [ + "src/user/mutations/update-user-password.js", + 54 + ] + ] + }, + "Unable to update password. Please try again.": { + "origin": [ + [ + "src/user/mutations/update-user-password.js", + 64 + ], + [ + "src/user/mutations/update-user-password.js", + 115 + ] + ] + }, + "Unable to update password, current password does not match. Please try again.": { + "origin": [ + [ + "src/user/mutations/update-user-password.js", + 74 + ] + ] + }, + "Unable to update password, new passwords do not match. Please try again.": { + "origin": [ + [ + "src/user/mutations/update-user-password.js", + 86 + ] + ] + }, + "Unable to update password, passwords are required to be 12 characters or longer. Please try again.": { + "origin": [ + [ + "src/user/mutations/update-user-password.js", + 98 + ] + ] + }, + "Password was successfully updated.": { + "origin": [ + [ + "src/user/mutations/update-user-password.js", + 120 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/update-user-profile.js.json b/api/src/locale/_build/src/user/mutations/update-user-profile.js.json new file mode 100644 index 0000000000..8c5b82eed9 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/update-user-profile.js.json @@ -0,0 +1,30 @@ +{ + "Authentication error, please sign in again.": { + "origin": [ + [ + "src/user/mutations/update-user-profile.js", + 67 + ] + ] + }, + "Unable to update profile. Please try again.": { + "origin": [ + [ + "src/user/mutations/update-user-profile.js", + 77 + ], + [ + "src/user/mutations/update-user-profile.js", + 147 + ] + ] + }, + "Profile successfully updated.": { + "origin": [ + [ + "src/user/mutations/update-user-profile.js", + 152 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/verify-account.js.json b/api/src/locale/_build/src/user/mutations/verify-account.js.json new file mode 100644 index 0000000000..d860cd264e --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/verify-account.js.json @@ -0,0 +1,38 @@ +{ + "Unable to verify account. Please try again.": { + "origin": [ + [ + "src/user/mutations/verify-account.js", + 42 + ], + [ + "src/user/mutations/verify-account.js", + 52 + ], + [ + "src/user/mutations/verify-account.js", + 93 + ] + ] + }, + "Unable to verify account. Please request a new email.": { + "origin": [ + [ + "src/user/mutations/verify-account.js", + 67 + ], + [ + "src/user/mutations/verify-account.js", + 77 + ] + ] + }, + "Successfully email verified account, and set TFA send method to email.": { + "origin": [ + [ + "src/user/mutations/verify-account.js", + 102 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/mutations/verify-phone-number.js.json b/api/src/locale/_build/src/user/mutations/verify-phone-number.js.json new file mode 100644 index 0000000000..8b953c1027 --- /dev/null +++ b/api/src/locale/_build/src/user/mutations/verify-phone-number.js.json @@ -0,0 +1,38 @@ +{ + "Authentication error, please sign in again.": { + "origin": [ + [ + "src/user/mutations/verify-phone-number.js", + 34 + ] + ] + }, + "Unable to two factor authenticate. Please try again.": { + "origin": [ + [ + "src/user/mutations/verify-phone-number.js", + 45 + ], + [ + "src/user/mutations/verify-phone-number.js", + 54 + ], + [ + "src/user/mutations/verify-phone-number.js", + 64 + ], + [ + "src/user/mutations/verify-phone-number.js", + 81 + ] + ] + }, + "Successfully verified phone number, and set TFA send method to text.": { + "origin": [ + [ + "src/user/mutations/verify-phone-number.js", + 91 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/queries/find-user-by-username.js.json b/api/src/locale/_build/src/user/queries/find-user-by-username.js.json new file mode 100644 index 0000000000..0be7f4714b --- /dev/null +++ b/api/src/locale/_build/src/user/queries/find-user-by-username.js.json @@ -0,0 +1,10 @@ +{ + "User could not be queried.": { + "origin": [ + [ + "src/user/queries/find-user-by-username.js", + 41 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/user/queries/is-user-admin.js.json b/api/src/locale/_build/src/user/queries/is-user-admin.js.json new file mode 100644 index 0000000000..648c4bdbf8 --- /dev/null +++ b/api/src/locale/_build/src/user/queries/is-user-admin.js.json @@ -0,0 +1,10 @@ +{ + "Unable to verify if user is an admin, please try again.": { + "origin": [ + [ + "src/user/queries/is-user-admin.js", + 23 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-by-domain.js.json b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-by-domain.js.json new file mode 100644 index 0000000000..a10d6a388c --- /dev/null +++ b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-by-domain.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find verified domain. Please try again.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-by-domain.js", + 23 + ], + [ + "src/verified-domains/loaders/load-verified-domain-by-domain.js", + 37 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-by-key.js.json b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-by-key.js.json new file mode 100644 index 0000000000..9b469adecd --- /dev/null +++ b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find verified domain. Please try again.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-by-key.js", + 23 + ], + [ + "src/verified-domains/loaders/load-verified-domain-by-key.js", + 37 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js.json b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js.json new file mode 100644 index 0000000000..3755dd8069 --- /dev/null +++ b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `verifiedDomain` connection.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 32 + ] + ] + }, + "Passing both `first` and `last` to paginate the `verifiedDomain` connection is not supported.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 41 + ] + ] + }, + "`{argSet}` on the `verifiedDomain` connection cannot be less than zero.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 53 + ] + ] + }, + "Requesting `{amount}` records on the `verifiedDomain` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 64 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 79 + ] + ] + }, + "Unable to load verified domains. Please try again.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 136 + ], + [ + "src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js", + 148 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-connections.js.json b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-connections.js.json new file mode 100644 index 0000000000..13600218ff --- /dev/null +++ b/api/src/locale/_build/src/verified-domains/loaders/load-verified-domain-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `verifiedDomain` connection.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 32 + ] + ] + }, + "Passing both `first` and `last` to paginate the `verifiedDomain` connection is not supported.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 41 + ] + ] + }, + "`{argSet}` on the `verifiedDomain` connection cannot be less than zero.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 53 + ] + ] + }, + "Requesting `{amount}` records on the `verifiedDomain` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 64 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 79 + ] + ] + }, + "Unable to load domains. Please try again.": { + "origin": [ + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 141 + ], + [ + "src/verified-domains/loaders/load-verified-domain-connections.js", + 151 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-domains/queries/find-verified-domain-by-domain.js.json b/api/src/locale/_build/src/verified-domains/queries/find-verified-domain-by-domain.js.json new file mode 100644 index 0000000000..129c8c1d6e --- /dev/null +++ b/api/src/locale/_build/src/verified-domains/queries/find-verified-domain-by-domain.js.json @@ -0,0 +1,10 @@ +{ + "No verified domain with the provided domain could be found.": { + "origin": [ + [ + "src/verified-domains/queries/find-verified-domain-by-domain.js", + 33 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-by-key.js.json b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-by-key.js.json new file mode 100644 index 0000000000..d4b4a5116b --- /dev/null +++ b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find verified organization. Please try again.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-by-key.js", + 21 + ], + [ + "src/verified-organizations/loaders/load-verified-organization-by-key.js", + 35 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-by-slug.js.json b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-by-slug.js.json new file mode 100644 index 0000000000..dccf873760 --- /dev/null +++ b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-by-slug.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find verified organization. Please try again.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-by-slug.js", + 21 + ], + [ + "src/verified-organizations/loaders/load-verified-organization-by-slug.js", + 33 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js.json b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js.json new file mode 100644 index 0000000000..bad2311766 --- /dev/null +++ b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `verifiedOrganization` connection.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 30 + ] + ] + }, + "Passing both `first` and `last` to paginate the `verifiedOrganization` connection is not supported.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 39 + ] + ] + }, + "`{argSet}` on the `verifiedOrganization` connection cannot be less than zero.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 51 + ] + ] + }, + "Requesting `{amount}` records on the `verifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 62 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 77 + ] + ] + }, + "Unable to load verified organizations. Please try again.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 136 + ], + [ + "src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js", + 148 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organizations-connections.js.json b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organizations-connections.js.json new file mode 100644 index 0000000000..55d78332e6 --- /dev/null +++ b/api/src/locale/_build/src/verified-organizations/loaders/load-verified-organizations-connections.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `verifiedOrganization` connection.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 31 + ] + ] + }, + "Passing both `first` and `last` to paginate the `verifiedOrganization` connection is not supported.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 40 + ] + ] + }, + "`{argSet}` on the `verifiedOrganization` connection cannot be less than zero.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 52 + ] + ] + }, + "Requesting `{amount}` records on the `verifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 63 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 78 + ] + ] + }, + "Unable to load verified organizations. Please try again.": { + "origin": [ + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 138 + ], + [ + "src/verified-organizations/loaders/load-verified-organizations-connections.js", + 150 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/verified-organizations/queries/find-verified-organization-by-slug.js.json b/api/src/locale/_build/src/verified-organizations/queries/find-verified-organization-by-slug.js.json new file mode 100644 index 0000000000..eae80239c3 --- /dev/null +++ b/api/src/locale/_build/src/verified-organizations/queries/find-verified-organization-by-slug.js.json @@ -0,0 +1,10 @@ +{ + "No organization with the provided slug could be found.": { + "origin": [ + [ + "src/verified-organizations/queries/find-verified-organization-by-slug.js", + 34 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/web-scan/loaders/load-https-by-key.js.json b/api/src/locale/_build/src/web-scan/loaders/load-https-by-key.js.json new file mode 100644 index 0000000000..9c750e2ef4 --- /dev/null +++ b/api/src/locale/_build/src/web-scan/loaders/load-https-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find https scan. Please try again.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-by-key.js", + 17 + ], + [ + "src/web-scan/loaders/load-https-by-key.js", + 29 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/web-scan/loaders/load-https-connections-by-domain-id.js.json b/api/src/locale/_build/src/web-scan/loaders/load-https-connections-by-domain-id.js.json new file mode 100644 index 0000000000..365ceb66d8 --- /dev/null +++ b/api/src/locale/_build/src/web-scan/loaders/load-https-connections-by-domain-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `https` connection.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 40 + ] + ] + }, + "Passing both `first` and `last` to paginate the `https` connection is not supported.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 49 + ] + ] + }, + "`{argSet}` on the `https` connection cannot be less than zero.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 61 + ] + ] + }, + "Requesting {amount} records on the `https` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 72 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 87 + ] + ] + }, + "Unable to load https scans. Please try again.": { + "origin": [ + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 143 + ], + [ + "src/web-scan/loaders/load-https-connections-by-domain-id.js", + 153 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/web-scan/loaders/load-ssl-by-key.js.json b/api/src/locale/_build/src/web-scan/loaders/load-ssl-by-key.js.json new file mode 100644 index 0000000000..5cd33d9ef0 --- /dev/null +++ b/api/src/locale/_build/src/web-scan/loaders/load-ssl-by-key.js.json @@ -0,0 +1,14 @@ +{ + "Unable to find ssl scan. Please try again.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-by-key.js", + 17 + ], + [ + "src/web-scan/loaders/load-ssl-by-key.js", + 29 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/_build/src/web-scan/loaders/load-ssl-connections-by-domain-id.js.json b/api/src/locale/_build/src/web-scan/loaders/load-ssl-connections-by-domain-id.js.json new file mode 100644 index 0000000000..4903f953cb --- /dev/null +++ b/api/src/locale/_build/src/web-scan/loaders/load-ssl-connections-by-domain-id.js.json @@ -0,0 +1,54 @@ +{ + "You must provide a `first` or `last` value to properly paginate the `ssl` connection.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 40 + ] + ] + }, + "Passing both `first` and `last` to paginate the `ssl` connection is not supported.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 49 + ] + ] + }, + "`{argSet}` on the `ssl` connection cannot be less than zero.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 61 + ] + ] + }, + "Requesting {amount} records on the `ssl` connection exceeds the `{argSet}` limit of 100 records.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 72 + ] + ] + }, + "`{argSet}` must be of type `number` not `{typeSet}`.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 87 + ] + ] + }, + "Unable to load ssl scans. Please try again.": { + "origin": [ + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 143 + ], + [ + "src/web-scan/loaders/load-ssl-connections-by-domain-id.js", + 153 + ] + ] + } +} \ No newline at end of file diff --git a/api/src/locale/en/messages.js b/api/src/locale/en/messages.js new file mode 100644 index 0000000000..1000ee7acc --- /dev/null +++ b/api/src/locale/en/messages.js @@ -0,0 +1 @@ +/*eslint-disable*/module.exports={messages:JSON.parse("{\"Eoi1qW\":[\"`\",[\"argSet\"],\"` must be of type `number` not `\",[\"typeSet\"],\"`.\"],\"uPlrHl\":[\"`\",[\"argSet\"],\"` on the `Affiliation` connection cannot be less than zero.\"],\"EtaVdR\":[\"`\",[\"argSet\"],\"` on the `DKIM` connection cannot be less than zero.\"],\"2npn9d\":[\"`\",[\"argSet\"],\"` on the `DkimFailureTable` connection cannot be less than zero.\"],\"mpaL2O\":[\"`\",[\"argSet\"],\"` on the `DKIMResults` connection cannot be less than zero.\"],\"2vz4+a\":[\"`\",[\"argSet\"],\"` on the `DMARC` connection cannot be less than zero.\"],\"ujwWhu\":[\"`\",[\"argSet\"],\"` on the `DmarcFailureTable` connection cannot be less than zero.\"],\"HbNnNq\":[\"`\",[\"argSet\"],\"` on the `DmarcSummaries` connection cannot be less than zero.\"],\"eSKAjw\":[\"`\",[\"argSet\"],\"` on the `Domain` connection cannot be less than zero.\"],\"R2ityD\":[\"`\",[\"argSet\"],\"` on the `FullPassTable` connection cannot be less than zero.\"],\"dqZo9M\":[\"`\",[\"argSet\"],\"` on the `GuidanceTag` connection cannot be less than zero.\"],\"YXZolK\":[\"`\",[\"argSet\"],\"` on the `HTTPS` connection cannot be less than zero.\"],\"UACahy\":[\"`\",[\"argSet\"],\"` on the `Log` connection cannot be less than zero.\"],\"nkUPC9\":[\"`\",[\"argSet\"],\"` on the `Organization` connection cannot be less than zero.\"],\"Jkk2ml\":[\"`\",[\"argSet\"],\"` on the `SPF` connection cannot be less than zero.\"],\"Rfl7ba\":[\"`\",[\"argSet\"],\"` on the `SpfFailureTable` connection cannot be less than zero.\"],\"w6BMWR\":[\"`\",[\"argSet\"],\"` on the `SSL` connection cannot be less than zero.\"],\"kk1tk/\":[\"`\",[\"argSet\"],\"` on the `User` connection cannot be less than zero.\"],\"6ZERsG\":[\"`\",[\"argSet\"],\"` on the `VerifiedDomain` connection cannot be less than zero.\"],\"plfOI4\":[\"`\",[\"argSet\"],\"` on the `VerifiedOrganization` connection cannot be less than zero.\"],\"w3MeQ2\":\"Authentication error. Please sign in.\",\"9QA26B\":\"Cannot query additional findings without permission.\",\"OEzMB0\":\"Cannot query affiliations on organization without admin permission or higher.\",\"2K0+0j\":\"Cannot query audit logs on organization without admin permission or higher.\",\"Z2VjXv\":\"Cannot query dns scan results without permission.\",\"SsrKFI\":\"Cannot query domain selectors without permission.\",\"/fzcPY\":\"Cannot query web scan results without permission.\",\"uWrncA\":\"CVE is already ignored for this domain.\",\"messFf\":\"CVE is not ignored for this domain.\",\"K9BF0u\":\"Email already in use.\",\"kr40ez\":\"Error while requesting scan. Please try again.\",\"xty1cI\":\"If an account with this username is found, a password reset link will be found in your inbox.\",\"n3yyu8\":\"If an account with this username is found, an email verification link will be found in your inbox.\",\"YPojZ6\":\"Incorrect TFA code. Please sign in again.\",\"q/Mmq8\":\"Incorrect token value. Please request a new email.\",\"MXwSlX\":\"Incorrect username or password. Please try again.\",\"1YEhLV\":\"Invalid token, please sign in.\",\"ydzoW2\":\"Message dismissed successfully\",\"6Ylhna\":\"New passwords do not match.\",\"BwHoFc\":\"No organization with the provided slug could be found.\",\"MnkZ16\":\"No verified domain with the provided domain could be found.\",\"L9hADR\":\"No verified organization with the provided slug could be found.\",\"uYSCJj\":\"Organization has already been verified.\",\"p20l3d\":\"Organization name already in use, please choose another and try again.\",\"SVGjT0\":\"Organization name already in use. Please try again with a different name.\",\"dndFWA\":\"Ownership check error. Unable to request domain information.\",\"Drhmz3\":\"Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.\",\"85YUUC\":\"Passing both `first` and `last` to paginate the `DKIM` connection is not supported.\",\"ELjtwQ\":\"Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.\",\"fh3YJO\":\"Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.\",\"x4tq+e\":\"Passing both `first` and `last` to paginate the `DMARC` connection is not supported.\",\"594X4Y\":\"Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.\",\"X0oiNk\":\"Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.\",\"k0nHGG\":\"Passing both `first` and `last` to paginate the `Domain` connection is not supported.\",\"qvXcAG\":\"Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.\",\"iC7wND\":\"Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.\",\"oiwNsK\":\"Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.\",\"UQKJcw\":\"Passing both `first` and `last` to paginate the `Log` connection is not supported.\",\"9QRMXj\":\"Passing both `first` and `last` to paginate the `Organization` connection is not supported.\",\"y0439x\":\"Passing both `first` and `last` to paginate the `SPF` connection is not supported.\",\"kp3U0m\":\"Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.\",\"o4LzIu\":\"Passing both `first` and `last` to paginate the `SSL` connection is not supported.\",\"eMCdJf\":\"Passing both `first` and `last` to paginate the `User` connection is not supported.\",\"9lvjn+\":\"Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.\",\"diyob+\":\"Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.\",\"t0Yhq7\":\"Password does not meet requirements.\",\"JweT52\":\"Password was successfully reset.\",\"U7wcn8\":\"Password was successfully updated.\",\"fDGOiR\":\"Passwords do not match.\",\"VJ90eS\":\"Permission check error. Unable to request domain information.\",\"smjfcW\":\"Permission Denied: Could not retrieve specified organization.\",\"6b0Uzo\":\"Permission Denied: Multi-factor authentication is required for admin accounts\",\"d7fLWy\":\"Permission Denied: Please contact org owner to transfer ownership.\",\"xBt+I5\":\"Permission Denied: Please contact organization admin for help with archiving domains.\",\"qqp3YV\":\"Permission Denied: Please contact organization admin for help with removing domain.\",\"9ty0BN\":\"Permission Denied: Please contact organization admin for help with removing domains.\",\"XLwFgy\":\"Permission Denied: Please contact organization admin for help with removing organization.\",\"AJ7T8X\":\"Permission Denied: Please contact organization admin for help with removing users.\",\"9MP0sV\":\"Permission Denied: Please contact organization admin for help with updating organization.\",\"9/MaeX\":\"Permission Denied: Please contact organization admin for help with updating user roles.\",\"gJAEM/\":\"Permission Denied: Please contact organization admin for help with user invitations.\",\"b+aSK2\":\"Permission Denied: Please contact organization admin for help with user role changes.\",\"MkIId8\":\"Permission Denied: Please contact organization user for help with creating domain.\",\"zCsJJ+\":\"Permission Denied: Please contact organization user for help with creating domains.\",\"zXXF5h\":\"Permission Denied: Please contact organization user for help with retrieving this domain.\",\"wI7ogy\":\"Permission Denied: Please contact organization user for help with scanning this domain.\",\"MU2+6C\":\"Permission Denied: Please contact organization user for help with updating this domain.\",\"WO5Xev\":\"Permission Denied: Please contact super admin for help with archiving organization.\",\"uf+M40\":\"Permission Denied: Please contact super admin for help with removing domain.\",\"Sf3vFY\":\"Permission Denied: Please contact super admin for help with removing organization.\",\"kJ+fSd\":\"Permission Denied: Please contact super admin for help with scanning this domain.\",\"c11gmL\":\"Permission Denied: Please contact super admin for help with verifying this organization.\",\"Kwq7PZ\":\"Permission error, not an admin for this user.\",\"ht7RB9\":\"Permission error: Unable to close other user's account.\",\"S3055l\":\"Permissions error. You do not have sufficient permissions to access this data.\",\"qj6rps\":\"Phone number has been successfully removed.\",\"uEAFen\":\"Phone number has been successfully set, you will receive a verification text message shortly.\",\"LbgZEx\":\"Please provide a comment when adding an outside domain.\",\"yu5LUi\":\"Profile successfully updated.\",\"22PG3h\":[\"Requesting \",[\"amount\"],\" records on the `DKIM` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"lEZ2x7\":[\"Requesting \",[\"amount\"],\" records on the `DKIMResults` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"3kc6YF\":[\"Requesting \",[\"amount\"],\" records on the `DMARC` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"wxwMqQ\":[\"Requesting \",[\"amount\"],\" records on the `HTTPS` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"qAmoqx\":[\"Requesting \",[\"amount\"],\" records on the `SPF` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"JD82Qj\":[\"Requesting \",[\"amount\"],\" records on the `SSL` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"ocE6aN\":[\"Requesting `\",[\"amount\"],\"` records on the `Affiliation` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"VF35kt\":[\"Requesting `\",[\"amount\"],\"` records on the `DkimFailureTable` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"fPydkO\":[\"Requesting `\",[\"amount\"],\"` records on the `DmarcFailureTable` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"zOGaYb\":[\"Requesting `\",[\"amount\"],\"` records on the `DmarcSummaries` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"oSdPzK\":[\"Requesting `\",[\"amount\"],\"` records on the `Domain` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"sfbRxl\":[\"Requesting `\",[\"amount\"],\"` records on the `FullPassTable` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"9ECkIl\":[\"Requesting `\",[\"amount\"],\"` records on the `GuidanceTag` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"u4JPdU\":[\"Requesting `\",[\"amount\"],\"` records on the `Log` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"UuKFug\":[\"Requesting `\",[\"amount\"],\"` records on the `Organization` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"ytIe4o\":[\"Requesting `\",[\"amount\"],\"` records on the `SpfFailureTable` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"5vNdM3\":[\"Requesting `\",[\"amount\"],\"` records on the `User` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"SfCdEm\":[\"Requesting `\",[\"amount\"],\"` records on the `VerifiedDomain` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"4Vvv6j\":[\"Requesting `\",[\"amount\"],\"` records on the `VerifiedOrganization` connection exceeds the `\",[\"argSet\"],\"` limit of 100 records.\"],\"FPrtxO\":[\"Successfully added \",[\"domainCount\"],\" domain(s) to \",[\"0\"],\".\"],\"aNKZ2s\":[\"Successfully added \",[\"domainCount\"],\" domains to \",[\"0\"],\".\"],\"6kD2ow\":[\"Successfully archived organization: \",[\"0\"],\".\"],\"du9Xou\":\"Successfully closed account.\",\"/XMSy3\":\"Successfully dispatched one time scan.\",\"EknPJ9\":\"Successfully dispatched subdomain discovery scan.\",\"WnhHoM\":\"Successfully email verified account, and set TFA send method to email.\",\"ZekZu1\":\"Successfully email verified account.\",\"v/sjus\":\"Successfully invited user to organization, and sent notification email.\",\"NOS/E2\":[\"Successfully left organization: \",[\"0\"]],\"Mb7h0p\":[\"Successfully removed \",[\"domainCount\"],\" domain(s) from \",[\"0\"],\".\"],\"whT9qL\":[\"Successfully removed \",[\"domainCount\"],\" domains from \",[\"0\"],\".\"],\"KJoIYL\":[\"Successfully removed domain: \",[\"0\"],\" from \",[\"1\"],\".\"],\"B3bhOU\":[\"Successfully removed domain: \",[\"0\"],\" from favourites.\"],\"6UbMHy\":[\"Successfully removed organization: \",[\"0\"],\".\"],\"m3IqsS\":\"Successfully removed user from organization.\",\"whspKB\":\"Successfully requested invite to organization, and sent notification email.\",\"hsTfau\":\"Successfully sent invitation to service, and organization email.\",\"92cunG\":\"Successfully signed out.\",\"UhOnUX\":[\"Successfully transferred org: \",[\"0\"],\" ownership to user: \",[\"1\"]],\"QLcXYT\":[\"Successfully verified organization: \",[\"0\"],\".\"],\"0UHOyT\":\"Successfully verified phone number, and set TFA send method to text.\",\"G+Imw/\":\"Token value incorrect, please sign in again.\",\"bTMB63\":\"Too many failed login attempts, please reset your password, and try again.\",\"KPk8On\":\"Tour completion confirmed successfully\",\"ghwok7\":\"Two factor code is incorrect. Please try again.\",\"to2nZU\":\"Two factor code length is incorrect. Please try again.\",\"4G8XnK\":\"Unable leave organization. Please try again.\",\"VtK4v3\":\"Unable to add domains in unknown organization.\",\"5c9XSu\":\"Unable to archive organization. Please try again.\",\"d0IZcx\":\"Unable to archive unknown organization.\",\"iGBFKx\":\"Unable to authenticate. Please try again.\",\"hGYodY\":\"Unable to check permission. Please try again.\",\"JBqBmK\":\"Unable to close account of an undefined user.\",\"ASvI35\":\"Unable to close account. Please try again.\",\"lYCFPq\":\"Unable to confirm completion of the tour. Please try again.\",\"JH9/Jq\":\"Unable to create domain in unknown organization.\",\"TUFqYc\":\"Unable to create domain, organization has already claimed it.\",\"PnTl0X\":\"Unable to create domain. Please try again.\",\"VkUlo7\":\"Unable to create domains. Please try again.\",\"Bk0F/3\":\"Unable to create organization. Please try again.\",\"ggS9AU\":\"Unable to discover domains for unknown organization.\",\"HLFEUv\":\"Unable to dismiss message. Please try again.\",\"U8WJkK\":\"Unable to dispatch one time scan. Please try again.\",\"sX31Qs\":\"Unable to export organization. Please try again.\",\"QDd6b7\":\"Unable to favourite domain, user has already favourited it.\",\"+4vwFL\":\"Unable to favourite domain. Please try again.\",\"WNzPnw\":\"Unable to favourite unknown domain.\",\"lPIuFw\":\"Unable to find Aggregate guidance tag(s). Please try again.\",\"q4hBrf\":\"Unable to find DKIM guidance tag(s). Please try again.\",\"BC7dKY\":\"Unable to find DKIM result(s). Please try again.\",\"C5fP4H\":\"Unable to find DKIM scan(s). Please try again.\",\"GH27F7\":\"Unable to find DMARC guidance tag(s). Please try again.\",\"ngV4gO\":\"Unable to find DMARC scan(s). Please try again.\",\"GtHb3j\":\"Unable to find DMARC summary data. Please try again.\",\"ULGdf/\":\"Unable to find DNS scan(s). Please try again.\",\"kzp3/l\":\"Unable to find guidance tag(s). Please try again.\",\"lvuEwe\":\"Unable to find HTTPS guidance tag(s). Please try again.\",\"eEnZq2\":\"Unable to find HTTPS scan(s). Please try again.\",\"8UI/ez\":\"Unable to find SPF guidance tag(s). Please try again.\",\"EJbuMu\":\"Unable to find SPF scan(s). Please try again.\",\"jy/9PC\":\"Unable to find SSL guidance tag(s). Please try again.\",\"xsn+ev\":\"Unable to find SSL scan(s). Please try again.\",\"itNwLs\":\"Unable to find the requested domain.\",\"AzNyVV\":\"Unable to find user affiliation(s). Please try again.\",\"usyHv4\":\"Unable to find verified organization(s). Please try again.\",\"J0A2iO\":\"Unable to ignore CVE. Please try again.\",\"QlRbEs\":\"Unable to invite user to organization. Please try again.\",\"Y3CReu\":\"Unable to invite user to organization. User is already affiliated with organization.\",\"4YaHho\":\"Unable to invite user to unknown organization.\",\"6DMjZ8\":\"Unable to invite user. Please try again.\",\"mFUvni\":\"Unable to invite yourself to an org.\",\"RuV5QP\":\"Unable to leave organization. Please try again.\",\"pAAJtC\":\"Unable to leave undefined organization.\",\"NRzPen\":\"Unable to load additional findings. Please try again.\",\"Q2fyq2\":\"Unable to load affiliation information. Please try again.\",\"dLE6X6\":\"Unable to load affiliation(s). Please try again.\",\"tEuM0Y\":\"Unable to load Aggregate guidance tag(s). Please try again.\",\"z6WNrg\":\"Unable to load all organization domain statuses. Please try again.\",\"AS+XYm\":\"Unable to load chart summary data. Please try again.\",\"VZSEkv\":\"Unable to load DKIM failure data. Please try again.\",\"iHCZPz\":\"Unable to load DKIM guidance tag(s). Please try again.\",\"VYgxhl\":\"Unable to load DKIM result(s). Please try again.\",\"XmHD0n\":\"Unable to load DKIM scan(s). Please try again.\",\"H/foxs\":\"Unable to load DKIM summary. Please try again.\",\"DZfpKQ\":\"Unable to load DMARC failure data. Please try again.\",\"IWleX/\":\"Unable to load DMARC guidance tag(s). Please try again.\",\"9KOsEh\":\"Unable to load DMARC phase summary. Please try again.\",\"eNPs30\":\"Unable to load DMARC scan(s). Please try again.\",\"wKC+/J\":\"Unable to load DMARC summary data. Please try again.\",\"PF8L8T\":\"Unable to load DMARC summary. Please try again.\",\"zv2kI4\":\"Unable to load DNS scan(s). Please try again.\",\"+mdFBt\":\"Unable to load domain selector(s). Please try again.\",\"hpeR8m\":\"Unable to load domain. Please try again.\",\"f/tKr7\":\"Unable to load domain(s). Please try again.\",\"aHKeg4\":\"Unable to load full pass data. Please try again.\",\"3fphqj\":\"Unable to load guidance tag(s). Please try again.\",\"cCar8N\":\"Unable to load HTTPS guidance tag(s). Please try again.\",\"7A73DJ\":\"Unable to load HTTPS scan(s). Please try again.\",\"BMKKFn\":\"Unable to load HTTPS summary. Please try again.\",\"hZEouI\":\"Unable to load log. Please try again.\",\"lSzsy0\":\"Unable to load log(s). Please try again.\",\"wrwKt2\":\"Unable to load mail summary. Please try again.\",\"Fpm2AP\":\"Unable to load organization domain statuses. Please try again.\",\"p6Lm32\":\"Unable to load organization summary data. Please try again.\",\"8KT8ti\":\"Unable to load organization(s). Please try again.\",\"LvHBFL\":\"Unable to load owner information. Please try again.\",\"sXOtBn\":\"Unable to load SPF failure data. Please try again.\",\"IQkbNl\":\"Unable to load SPF guidance tag(s). Please try again.\",\"FHxRJZ\":\"Unable to load SPF scan(s). Please try again.\",\"ZsscmB\":\"Unable to load SPF summary. Please try again.\",\"HI7CSw\":\"Unable to load SSL guidance tag(s). Please try again.\",\"tJYOji\":\"Unable to load SSL scan(s). Please try again.\",\"PpwpCp\":\"Unable to load SSL summary. Please try again.\",\"xzKtay\":\"Unable to load summary. Please try again.\",\"y7ER1b\":\"Unable to load tags(s). Please try again.\",\"PzFOAc\":\"Unable to load user(s). Please try again.\",\"Ph65og\":\"Unable to load verified domain(s). Please try again.\",\"QdFjoo\":\"Unable to load verified organization(s). Please try again.\",\"MjZQ9y\":\"Unable to load verified rua domains. Please try again.\",\"WMM6DC\":\"Unable to load web connections summary. Please try again.\",\"szMW1T\":\"Unable to load web scan(s). Please try again.\",\"OEKyZJ\":\"Unable to load web summary. Please try again.\",\"j4fHlS\":\"Unable to query affiliation(s). Please try again.\",\"c0mbW+\":\"Unable to query domain(s). Please try again.\",\"7W4MNs\":\"Unable to query log(s). Please try again.\",\"098Gys\":\"Unable to query user(s). Please try again.\",\"KfROBu\":\"Unable to refresh tokens, please sign in.\",\"Hls3tJ\":\"Unable to remove a user that already does not belong to this organization.\",\"ELijnP\":\"Unable to remove domain from unknown organization.\",\"nuxrPH\":\"Unable to remove domain. Domain is not part of organization.\",\"hHXji3\":\"Unable to remove domain. Please try again.\",\"iMzlxt\":\"Unable to remove domains from unknown organization.\",\"X34ZOQ\":\"Unable to remove organization. Please try again.\",\"Qf8OvV\":\"Unable to remove phone number. Please try again.\",\"GXX5w7\":\"Unable to remove unknown domain.\",\"pQ0cLH\":\"Unable to remove unknown organization.\",\"gWw+wU\":\"Unable to remove unknown user from organization.\",\"APaKFI\":\"Unable to remove user from organization.\",\"v28SUT\":\"Unable to remove user from this organization. Please try again.\",\"qjq1fL\":\"Unable to remove user from unknown organization.\",\"Wu3IdK\":\"Unable to request a one time scan on a domain that already has a pending scan.\",\"s79a/Q\":\"Unable to request a one time scan on an unknown domain.\",\"BlpeBr\":\"Unable to request a one time scan. Please try again.\",\"YlWZ/S\":\"Unable to request a subdomain discovery scan on an invalid domain.\",\"/rRkGm\":\"Unable to request a subdomain discovery scan on an unknown domain.\",\"qTisBR\":\"Unable to request invite to organization with which you are already affiliated.\",\"1+X6yw\":\"Unable to request invite to organization with which you have already requested to join.\",\"Djnfqd\":\"Unable to request invite to unknown organization.\",\"LlyhW+\":\"Unable to request invite. Please try again.\",\"NQWBIw\":\"Unable to reset password. Please request a new email.\",\"dJ2lzx\":\"Unable to reset password. Please try again.\",\"t2wESI\":[\"Unable to retrieve DMARC report information for: \",[\"domain\"]],\"OTycVQ\":\"Unable to select DMARC report(s) for this period and year.\",\"qT4MFW\":\"Unable to send authentication email. Please try again.\",\"LPodG6\":\"Unable to send authentication text message. Please try again.\",\"9TsNGX\":\"Unable to send org invite email. Please try again.\",\"zwqufn\":\"Unable to send org invite request email. Please try again.\",\"1rL9I7\":\"Unable to send password reset email. Please try again.\",\"svsSOK\":\"Unable to send two factor authentication message. Please try again.\",\"ujyB0i\":\"Unable to send updated username email. Please try again.\",\"02Keb+\":\"Unable to send verification email. Please try again.\",\"E4JeKk\":\"Unable to set phone number, please try again.\",\"g3Svfo\":\"Unable to sign in, please try again.\",\"VqhI6Q\":\"Unable to sign up, please contact org admin for a new invite.\",\"2hEUmM\":\"Unable to sign up. Please try again.\",\"7/HZU+\":\"Unable to stop ignoring CVE. Please try again.\",\"JLEqkA\":\"Unable to transfer organization ownership. Please try again.\",\"U1j4ef\":\"Unable to transfer ownership of a verified organization.\",\"c3WjG6\":\"Unable to transfer ownership of an org to an undefined user.\",\"TMjnAo\":\"Unable to transfer ownership of undefined organization.\",\"rzoBBf\":\"Unable to transfer ownership to a user outside the org. Please invite the user and try again.\",\"8TSaAb\":\"Unable to two factor authenticate. Please try again.\",\"FHUz/I\":\"Unable to unfavourite domain, domain is not favourited.\",\"F7RRVM\":\"Unable to unfavourite domain. Please try again.\",\"RgT2Fw\":\"Unable to unfavourite unknown domain.\",\"iM9uiT\":\"Unable to unignore CVE. Please try again.\",\"CmZgVA\":\"Unable to update domain edge. Please try again.\",\"gAMNI6\":\"Unable to update domain in an unknown org.\",\"JbEMFm\":\"Unable to update domain that does not belong to the given organization.\",\"jHfpyF\":\"Unable to update domain. Please try again.\",\"0tE9dW\":\"Unable to update organization. Please try again.\",\"4Dutt5\":\"Unable to update password, current password does not match. Please try again.\",\"fNY+xk\":\"Unable to update password, new passwords do not match. Please try again.\",\"TK5yIB\":\"Unable to update password, passwords do not match requirements. Please try again.\",\"2OQWjx\":\"Unable to update password. Please try again.\",\"+q5Rwg\":\"Unable to update profile. Please try again.\",\"Zqql23\":\"Unable to update role: organization unknown.\",\"5g5CPf\":\"Unable to update role: user does not belong to organization.\",\"ygexeT\":\"Unable to update role: user unknown.\",\"K5a9HQ\":\"Unable to update unknown domain.\",\"4WmaEA\":\"Unable to update unknown organization.\",\"13kdVN\":\"Unable to update user's role. Please try again.\",\"xWavp5\":\"Unable to update your own role.\",\"Vs/Ux1\":\"Unable to verify account. Please request a new email.\",\"Z4+72s\":\"Unable to verify account. Please try again.\",\"Tqm26J\":\"Unable to verify if user is a super admin, please try again.\",\"KPWee0\":\"Unable to verify if user is an admin, please try again.\",\"apJs/U\":\"Unable to verify organization. Please try again.\",\"x5sf5T\":\"Unable to verify unknown organization.\",\"sesHCz\":\"User could not be queried.\",\"czGsbE\":\"User is trying to register for a non-production environment.\",\"Tgpvqi\":\"User role was updated successfully.\",\"iuCjUb\":\"Username not available, please try another.\",\"V/PS9h\":\"Verification error. Please activate multi-factor authentication to access content.\",\"7zQuGi\":\"Verification error. Please verify your account via email to access content.\",\"kMaiy6\":\"You must provide a `domainId` to retrieve a domain's additional findings.\",\"p+DYLk\":\"You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.\",\"c7s2Nw\":\"You must provide a `first` or `last` value to properly paginate the `DKIM` connection.\",\"SEEDiW\":\"You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.\",\"wlbfWc\":\"You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.\",\"lu3LE3\":\"You must provide a `first` or `last` value to properly paginate the `DMARC` connection.\",\"pDllMl\":\"You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.\",\"voJfyc\":\"You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.\",\"c8Qbmn\":\"You must provide a `first` or `last` value to properly paginate the `Domain` connection.\",\"ZacidG\":\"You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.\",\"mciz5o\":\"You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.\",\"87Zz6g\":\"You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.\",\"co0onA\":\"You must provide a `first` or `last` value to properly paginate the `Log` connection.\",\"thP+Yz\":\"You must provide a `first` or `last` value to properly paginate the `Organization` connection.\",\"Xfjual\":\"You must provide a `first` or `last` value to properly paginate the `SPF` connection.\",\"4s6W/Q\":\"You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.\",\"EsdwHf\":\"You must provide a `first` or `last` value to properly paginate the `SSL` connection.\",\"N+og2p\":\"You must provide a `first` or `last` value to properly paginate the `User` connection.\",\"EMHIWF\":\"You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.\",\"RMtxoi\":\"You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.\",\"3Zdrst\":\"You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.\",\"DRToPU\":\"You must provide a `limit` value in the range of 1-100 to properly paginate the `MXRecord` connection.\",\"NfXI/l\":\"You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.\",\"KAZHf6\":\"You must provide a `limit` value to properly paginate the `DNS` connection.\",\"uTt7kN\":\"You must provide a `limit` value to properly paginate the `MXRecord` connection.\",\"KyJdrc\":\"You must provide a `limit` value to properly paginate the `web` connection.\",\"TAJGTA\":\"You must provide a `period` value to access the `ChartSummaries` connection.\",\"DWa4my\":\"You must provide a `period` value to access the `DmarcSummaries` connection.\",\"XZZ7Lv\":\"You must provide a `period` value to access the `OrganizationSummaries` connection.\",\"oafMGl\":\"You must provide a `year` value to access the `ChartSummaries` connection.\",\"jqcUEU\":\"You must provide a `year` value to access the `DmarcSummaries` connection.\",\"z4RkNn\":\"You must provide a `year` value to access the `OrganizationSummaries` connection.\",\"JOssnw\":\"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.\",\"cfpKpu\":\"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `MXRecord` connection.\",\"O++odO\":\"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.\"}")}; \ No newline at end of file diff --git a/api/src/locale/en/messages.po b/api/src/locale/en/messages.po new file mode 100644 index 0000000000..554afd988e --- /dev/null +++ b/api/src/locale/en/messages.po @@ -0,0 +1,1911 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: \n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:121 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:131 +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:83 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:202 +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:178 +#: src/domain/loaders/load-domain-connections-by-user-id.js:205 +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:136 +#: src/organization/loaders/load-organization-connections-by-domain-id.js:203 +#: src/organization/loaders/load-organization-connections-by-user-id.js:213 +#: src/user/loaders/load-user-connections-by-user-id.js:145 +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:164 +#: src/verified-domains/loaders/load-verified-domain-connections.js:164 +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:214 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:212 +msgid "`{argSet}` must be of type `number` not `{typeSet}`." +msgstr "`{argSet}` must be of type `number` not `{typeSet}`." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:98 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:182 +msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." +msgstr "`{argSet}` on the `Affiliation` connection cannot be less than zero." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:135 +#~ msgid "`{argSet}` on the `DKIM` connection cannot be less than zero." +#~ msgstr "`{argSet}` on the `DKIM` connection cannot be less than zero." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." +msgstr "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:110 +#~ msgid "`{argSet}` on the `DKIMResults` connection cannot be less than zero." +#~ msgstr "`{argSet}` on the `DKIMResults` connection cannot be less than zero." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:160 +#~ msgid "`{argSet}` on the `DMARC` connection cannot be less than zero." +#~ msgstr "`{argSet}` on the `DMARC` connection cannot be less than zero." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." +msgstr "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:179 +msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." +msgstr "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:157 +#: src/domain/loaders/load-domain-connections-by-user-id.js:182 +msgid "`{argSet}` on the `Domain` connection cannot be less than zero." +msgstr "`{argSet}` on the `Domain` connection cannot be less than zero." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `FullPassTable` connection cannot be less than zero." +msgstr "`{argSet}` on the `FullPassTable` connection cannot be less than zero." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:106 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:110 +msgid "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." +msgstr "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:168 +#~ msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." +#~ msgstr "`{argSet}` on the `HTTPS` connection cannot be less than zero." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:112 +msgid "`{argSet}` on the `Log` connection cannot be less than zero." +msgstr "`{argSet}` on the `Log` connection cannot be less than zero." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:182 +#: src/organization/loaders/load-organization-connections-by-user-id.js:192 +msgid "`{argSet}` on the `Organization` connection cannot be less than zero." +msgstr "`{argSet}` on the `Organization` connection cannot be less than zero." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:154 +#~ msgid "`{argSet}` on the `SPF` connection cannot be less than zero." +#~ msgstr "`{argSet}` on the `SPF` connection cannot be less than zero." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." +msgstr "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:192 +#~ msgid "`{argSet}` on the `SSL` connection cannot be less than zero." +#~ msgstr "`{argSet}` on the `SSL` connection cannot be less than zero." + +#: src/user/loaders/load-user-connections-by-user-id.js:122 +msgid "`{argSet}` on the `User` connection cannot be less than zero." +msgstr "`{argSet}` on the `User` connection cannot be less than zero." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:138 +#: src/verified-domains/loaders/load-verified-domain-connections.js:138 +msgid "`{argSet}` on the `VerifiedDomain` connection cannot be less than zero." +msgstr "`{argSet}` on the `VerifiedDomain` connection cannot be less than zero." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:188 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:186 +msgid "`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero." +msgstr "`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero." + +#: src/organization/objects/organization.js:240 +#: src/organization/queries/get-all-organization-domain-statuses.js:69 +msgid "Assess" +msgstr "Assess" + +#: src/auth/checks/check-permission.js:18 +#: src/auth/checks/check-permission.js:57 +#: src/auth/guards/user-required.js:10 +#: src/auth/guards/user-required.js:21 +#: src/auth/guards/user-required.js:28 +#: src/auth/loaders/load-permission-by-org-id.js:19 +#: src/auth/loaders/load-permission-by-org-id.js:63 +msgid "Authentication error. Please sign in." +msgstr "Authentication error. Please sign in." + +#: src/domain/objects/domain.js:229 +msgid "Cannot query additional findings without permission." +msgstr "Cannot query additional findings without permission." + +#: src/organization/objects/organization.js:359 +msgid "Cannot query affiliations on organization without admin permission or higher." +msgstr "Cannot query affiliations on organization without admin permission or higher." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:224 +#: src/audit-logs/queries/find-audit-logs.js:53 +msgid "Cannot query audit logs on organization without admin permission or higher." +msgstr "Cannot query audit logs on organization without admin permission or higher." + +#: src/domain/objects/domain.js:164 +msgid "Cannot query dns scan results without permission." +msgstr "Cannot query dns scan results without permission." + +#: src/domain/objects/domain.js:65 +msgid "Cannot query domain selectors without permission." +msgstr "Cannot query domain selectors without permission." + +#: src/domain/objects/domain.js:206 +msgid "Cannot query web scan results without permission." +msgstr "Cannot query web scan results without permission." + +#: src/domain/mutations/ignore-cve.js:77 +msgid "CVE is already ignored for this domain." +msgstr "CVE is already ignored for this domain." + +#: src/domain/mutations/unignore-cve.js:77 +msgid "CVE is not ignored for this domain." +msgstr "CVE is not ignored for this domain." + +#: src/organization/objects/organization.js:242 +#: src/organization/queries/get-all-organization-domain-statuses.js:71 +msgid "Deploy" +msgstr "Deploy" + +#: src/user/mutations/sign-up.js:111 +msgid "Email already in use." +msgstr "Email already in use." + +#: src/organization/objects/organization.js:244 +#: src/organization/queries/get-all-organization-domain-statuses.js:73 +msgid "Enforce" +msgstr "Enforce" + +#: src/domain/mutations/request-scan.js:90 +#: src/domain/mutations/request-scan.js:100 +msgid "Error while requesting scan. Please try again." +msgstr "Error while requesting scan. Please try again." + +#: src/user/mutations/send-password-reset.js:61 +msgid "If an account with this username is found, a password reset link will be found in your inbox." +msgstr "If an account with this username is found, a password reset link will be found in your inbox." + +#: src/user/mutations/send-email-verification.js:60 +#~ msgid "If an account with this username is found, an email verification link will be found in your inbox." +#~ msgstr "If an account with this username is found, an email verification link will be found in your inbox." + +#: src/user/mutations/authenticate.js:221 +#: src/user/mutations/authenticate.js:229 +#: src/user/mutations/authenticate.js:231 +msgid "Incorrect TFA code. Please sign in again." +msgstr "Incorrect TFA code. Please sign in again." + +#: src/user/mutations/reset-password.js:61 +msgid "Incorrect token value. Please request a new email." +msgstr "Incorrect token value. Please request a new email." + +#: src/user/mutations/sign-in.js:68 +#: src/user/mutations/sign-in.js:285 +msgid "Incorrect username or password. Please try again." +msgstr "Incorrect username or password. Please try again." + +#: src/auth/utils/verify-jwt.js:15 +msgid "Invalid token, please sign in." +msgstr "Invalid token, please sign in." + +#: src/organization/objects/organization.js:246 +#: src/organization/queries/get-all-organization-domain-statuses.js:75 +msgid "Maintain" +msgstr "Maintain" + +#: src/user/mutations/dismiss-message.js:75 +msgid "Message dismissed successfully" +msgstr "Message dismissed successfully" + +#: src/user/mutations/reset-password.js:88 +msgid "New passwords do not match." +msgstr "New passwords do not match." + +#: src/organization/queries/find-organization-by-slug.js:42 +#: src/user/queries/find-my-tracker.js:29 +msgid "No organization with the provided slug could be found." +msgstr "No organization with the provided slug could be found." + +#: src/verified-domains/queries/find-verified-domain-by-domain.js:24 +msgid "No verified domain with the provided domain could be found." +msgstr "No verified domain with the provided domain could be found." + +#: src/verified-organizations/queries/find-verified-organization-by-slug.js:24 +msgid "No verified organization with the provided slug could be found." +msgstr "No verified organization with the provided slug could be found." + +#: src/organization/mutations/verify-organization.js:78 +msgid "Organization has already been verified." +msgstr "Organization has already been verified." + +#: src/organization/mutations/update-organization.js:167 +msgid "Organization name already in use, please choose another and try again." +msgstr "Organization name already in use, please choose another and try again." + +#: src/organization/mutations/create-organization.js:85 +msgid "Organization name already in use. Please try again with a different name." +msgstr "Organization name already in use. Please try again with a different name." + +#: src/auth/checks/check-domain-ownership.js:29 +#: src/auth/checks/check-domain-ownership.js:39 +#: src/auth/checks/check-domain-ownership.js:65 +#: src/auth/checks/check-domain-ownership.js:74 +msgid "Ownership check error. Unable to request domain information." +msgstr "Ownership check error. Unable to request domain information." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:89 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:170 +msgid "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:123 +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:159 +#~ msgid "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." +#~ msgstr "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:98 +#~ msgid "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." +#~ msgstr "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:147 +#~ msgid "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." +#~ msgstr "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:170 +msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:148 +#: src/domain/loaders/load-domain-connections-by-user-id.js:173 +msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `Domain` connection is not supported." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:94 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:98 +msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:156 +#~ msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." +#~ msgstr "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:105 +msgid "Passing both `first` and `last` to paginate the `Log` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `Log` connection is not supported." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:173 +#: src/organization/loaders/load-organization-connections-by-user-id.js:185 +msgid "Passing both `first` and `last` to paginate the `Organization` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `Organization` connection is not supported." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:142 +#~ msgid "Passing both `first` and `last` to paginate the `SPF` connection is not supported." +#~ msgstr "Passing both `first` and `last` to paginate the `SPF` connection is not supported." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:180 +#~ msgid "Passing both `first` and `last` to paginate the `SSL` connection is not supported." +#~ msgstr "Passing both `first` and `last` to paginate the `SSL` connection is not supported." + +#: src/user/loaders/load-user-connections-by-user-id.js:113 +msgid "Passing both `first` and `last` to paginate the `User` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `User` connection is not supported." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:126 +#: src/verified-domains/loaders/load-verified-domain-connections.js:126 +msgid "Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:176 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:174 +msgid "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." +msgstr "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." + +#: src/user/mutations/reset-password.js:100 +#: src/user/mutations/sign-up.js:89 +msgid "Password does not meet requirements." +msgstr "Password does not meet requirements." + +#: src/user/mutations/reset-password.js:140 +msgid "Password was successfully reset." +msgstr "Password was successfully reset." + +#: src/user/mutations/update-user-password.js:109 +msgid "Password was successfully updated." +msgstr "Password was successfully updated." + +#: src/user/mutations/sign-up.js:99 +msgid "Passwords do not match." +msgstr "Passwords do not match." + +#: src/auth/checks/check-domain-permission.js:22 +#: src/auth/checks/check-domain-permission.js:51 +#: src/auth/checks/check-domain-permission.js:61 +#: src/auth/loaders/load-domain-permission-by-domain-id.js:19 +#: src/auth/loaders/load-domain-permission-by-domain-id.js:51 +#: src/auth/loaders/load-domain-permission-by-domain-id.js:61 +msgid "Permission check error. Unable to request domain information." +msgstr "Permission check error. Unable to request domain information." + +#: src/organization/queries/find-organization-by-slug.js:50 +#: src/organization/queries/find-organization-by-slug.js:52 +msgid "Permission Denied: Could not retrieve specified organization." +msgstr "Permission Denied: Could not retrieve specified organization." + +#: src/user/mutations/update-user-profile.js:109 +msgid "Permission Denied: Multi-factor authentication is required for admin accounts" +msgstr "Permission Denied: Multi-factor authentication is required for admin accounts" + +#: src/affiliation/mutations/transfer-org-ownership.js:74 +msgid "Permission Denied: Please contact org owner to transfer ownership." +msgstr "Permission Denied: Please contact org owner to transfer ownership." + +#: src/domain/mutations/remove-organizations-domains.js:128 +msgid "Permission Denied: Please contact organization admin for help with archiving domains." +msgstr "Permission Denied: Please contact organization admin for help with archiving domains." + +#: src/tags/mutations/create-tag.js:131 +msgid "Permission Denied: Please contact organization admin for help with creating tag." +msgstr "Permission Denied: Please contact organization admin for help with creating tag." + +#: src/domain/mutations/remove-domain.js:96 +msgid "Permission Denied: Please contact organization admin for help with removing domain." +msgstr "Permission Denied: Please contact organization admin for help with removing domain." + +#: src/domain/mutations/remove-organizations-domains.js:117 +msgid "Permission Denied: Please contact organization admin for help with removing domains." +msgstr "Permission Denied: Please contact organization admin for help with removing domains." + +#: src/organization/mutations/remove-organization.js:67 +msgid "Permission Denied: Please contact organization admin for help with removing organization." +msgstr "Permission Denied: Please contact organization admin for help with removing organization." + +#: src/affiliation/mutations/remove-user-from-org.js:128 +#: src/affiliation/mutations/remove-user-from-org.js:140 +msgid "Permission Denied: Please contact organization admin for help with removing users." +msgstr "Permission Denied: Please contact organization admin for help with removing users." + +#: src/domain/mutations/update-domains-by-domain-ids.js:81 +#: src/domain/mutations/update-domains-by-filters.js:89 +msgid "Permission Denied: Please contact organization admin for help with updating domains." +msgstr "Permission Denied: Please contact organization admin for help with updating domains." + +#: src/organization/mutations/update-organization.js:152 +msgid "Permission Denied: Please contact organization admin for help with updating organization." +msgstr "Permission Denied: Please contact organization admin for help with updating organization." + +#: src/tags/mutations/update-tag.js:125 +#: src/tags/mutations/update-tag.js:136 +msgid "Permission Denied: Please contact organization admin for help with updating tag." +msgstr "Permission Denied: Please contact organization admin for help with updating tag." + +#: src/affiliation/mutations/update-user-role.js:170 +#: src/affiliation/mutations/update-user-role.js:193 +#: src/affiliation/mutations/update-user-role.js:210 +#~ msgid "Permission Denied: Please contact organization admin for help with updating user roles." +#~ msgstr "Permission Denied: Please contact organization admin for help with updating user roles." + +#: src/affiliation/mutations/invite-user-to-org.js:99 +msgid "Permission Denied: Please contact organization admin for help with user invitations." +msgstr "Permission Denied: Please contact organization admin for help with user invitations." + +#: src/affiliation/mutations/update-user-role.js:112 +msgid "Permission Denied: Please contact organization admin for help with user role changes." +msgstr "Permission Denied: Please contact organization admin for help with user role changes." + +#: src/domain/mutations/create-domain.js:130 +msgid "Permission Denied: Please contact organization user for help with creating domain." +msgstr "Permission Denied: Please contact organization user for help with creating domain." + +#: src/domain/mutations/add-organizations-domains.js:122 +msgid "Permission Denied: Please contact organization user for help with creating domains." +msgstr "Permission Denied: Please contact organization user for help with creating domains." + +#: src/organization/objects/organization.js:111 +#~ msgid "Permission Denied: Please contact organization user for help with retrieving tags." +#~ msgstr "Permission Denied: Please contact organization user for help with retrieving tags." + +#: src/domain/queries/find-domain-by-domain.js:51 +#: src/organization/objects/organization.js:195 +msgid "Permission Denied: Please contact organization user for help with retrieving this domain." +msgstr "Permission Denied: Please contact organization user for help with retrieving this domain." + +#: src/domain/mutations/request-discovery.js:98 +#: src/domain/mutations/request-scan.js:66 +msgid "Permission Denied: Please contact organization user for help with scanning this domain." +msgstr "Permission Denied: Please contact organization user for help with scanning this domain." + +#: src/domain/mutations/update-domain.js:151 +msgid "Permission Denied: Please contact organization user for help with updating this domain." +msgstr "Permission Denied: Please contact organization user for help with updating this domain." + +#: src/organization/mutations/archive-organization.js:66 +msgid "Permission Denied: Please contact super admin for help with archiving organization." +msgstr "Permission Denied: Please contact super admin for help with archiving organization." + +#: src/domain/mutations/remove-domain.js:109 +#: src/domain/mutations/remove-organizations-domains.js:106 +msgid "Permission Denied: Please contact super admin for help with removing domain." +msgstr "Permission Denied: Please contact super admin for help with removing domain." + +#: src/organization/mutations/remove-organization.js:80 +msgid "Permission Denied: Please contact super admin for help with removing organization." +msgstr "Permission Denied: Please contact super admin for help with removing organization." + +#: src/domain/mutations/request-scan.js:67 +#~ msgid "Permission Denied: Please contact super admin for help with scanning this domain." +#~ msgstr "Permission Denied: Please contact super admin for help with scanning this domain." + +#: src/tags/mutations/update-tag.js:147 +msgid "Permission Denied: Please contact super admin for help with updating tag." +msgstr "Permission Denied: Please contact super admin for help with updating tag." + +#: src/affiliation/mutations/invite-user-to-org.js:112 +msgid "Permission Denied: Please contact super admin for help with user invitations." +msgstr "Permission Denied: Please contact super admin for help with user invitations." + +#: src/affiliation/mutations/update-user-role.js:167 +msgid "Permission Denied: Please contact super admin for help with user role changes." +msgstr "Permission Denied: Please contact super admin for help with user role changes." + +#: src/organization/mutations/verify-organization.js:65 +msgid "Permission Denied: Please contact super admin for help with verifying this organization." +msgstr "Permission Denied: Please contact super admin for help with verifying this organization." + +#: src/auth/checks/check-user-is-admin-for-user.js:20 +#: src/auth/checks/check-user-is-admin-for-user.js:30 +#: src/auth/checks/check-user-is-admin-for-user.js:63 +#: src/auth/checks/check-user-is-admin-for-user.js:73 +msgid "Permission error, not an admin for this user." +msgstr "Permission error, not an admin for this user." + +#: src/user/mutations/close-account.js:148 +msgid "Permission error: Unable to close other user's account." +msgstr "Permission error: Unable to close other user's account." + +#: src/auth/guards/super-admin-required.js:11 +msgid "Permissions error. You do not have sufficient permissions to access this data." +msgstr "Permissions error. You do not have sufficient permissions to access this data." + +#: src/user/mutations/remove-phone-number.js:65 +msgid "Phone number has been successfully removed." +msgstr "Phone number has been successfully removed." + +#: src/user/mutations/set-phone-number.js:118 +msgid "Phone number has been successfully set, you will receive a verification text message shortly." +msgstr "Phone number has been successfully set, you will receive a verification text message shortly." + +#: src/domain/mutations/create-domain.js:118 +#: src/domain/mutations/update-domain.js:124 +#~ msgid "Please provide a comment when adding an outside domain." +#~ msgstr "Please provide a comment when adding an outside domain." + +#: src/user/mutations/update-user-profile.js:188 +msgid "Profile successfully updated." +msgstr "Profile successfully updated." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:146 +#~ msgid "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:121 +#~ msgid "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:171 +#~ msgid "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:179 +#~ msgid "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:165 +#~ msgid "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:203 +#~ msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:107 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:193 +msgid "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:188 +msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:164 +#: src/domain/loaders/load-domain-connections-by-user-id.js:191 +msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:117 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:121 +msgid "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:119 +msgid "Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:189 +#: src/organization/loaders/load-organization-connections-by-user-id.js:199 +msgid "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records." + +#: src/user/loaders/load-user-connections-by-user-id.js:131 +msgid "Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:149 +#: src/verified-domains/loaders/load-verified-domain-connections.js:149 +msgid "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:199 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:197 +msgid "Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records." +msgstr "Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records." + +#. placeholder {0}: org.slug +#: src/domain/mutations/add-organizations-domains.js:334 +msgid "Successfully added {domainCount} domain(s) to {0}." +msgstr "Successfully added {domainCount} domain(s) to {0}." + +#: src/domain/mutations/add-organizations-domains.js:351 +#~ msgid "Successfully added {domainCount} domains to {0}." +#~ msgstr "Successfully added {domainCount} domains to {0}." + +#. placeholder {0}: organization.slug +#: src/organization/mutations/archive-organization.js:100 +msgid "Successfully archived organization: {0}." +msgstr "Successfully archived organization: {0}." + +#: src/user/mutations/close-account.js:96 +#: src/user/mutations/close-account.js:230 +msgid "Successfully closed account." +msgstr "Successfully closed account." + +#: src/domain/mutations/request-scan.js:175 +msgid "Successfully dispatched one time scan." +msgstr "Successfully dispatched one time scan." + +#: src/domain/mutations/request-discovery.js:134 +msgid "Successfully dispatched subdomain discovery scan." +msgstr "Successfully dispatched subdomain discovery scan." + +#: src/user/mutations/verify-account.js:97 +#~ msgid "Successfully email verified account, and set TFA send method to email." +#~ msgstr "Successfully email verified account, and set TFA send method to email." + +#: src/user/mutations/verify-account.js:143 +msgid "Successfully email verified account." +msgstr "Successfully email verified account." + +#: src/affiliation/mutations/invite-user-to-org.js:282 +msgid "Successfully invited user to organization, and sent notification email." +msgstr "Successfully invited user to organization, and sent notification email." + +#. placeholder {0}: org.slug +#: src/affiliation/mutations/leave-organization.js:86 +msgid "Successfully left organization: {0}" +msgstr "Successfully left organization: {0}" + +#. placeholder {0}: org.slug +#: src/domain/mutations/remove-organizations-domains.js:477 +msgid "Successfully removed {domainCount} domain(s) from {0}." +msgstr "Successfully removed {domainCount} domain(s) from {0}." + +#: src/domain/mutations/remove-organizations-domains.js:530 +#~ msgid "Successfully removed {domainCount} domains from {0}." +#~ msgstr "Successfully removed {domainCount} domains from {0}." + +#. placeholder {0}: domain.domain +#. placeholder {1}: org.slug +#: src/domain/mutations/remove-domain.js:373 +msgid "Successfully removed domain: {0} from {1}." +msgstr "Successfully removed domain: {0} from {1}." + +#. placeholder {0}: domain.domain +#: src/domain/mutations/unfavourite-domain.js:126 +msgid "Successfully removed domain: {0} from favourites." +msgstr "Successfully removed domain: {0} from favourites." + +#. placeholder {0}: organization.slug +#: src/organization/mutations/remove-organization.js:107 +msgid "Successfully removed organization: {0}." +msgstr "Successfully removed organization: {0}." + +#: src/affiliation/mutations/remove-user-from-org.js:201 +msgid "Successfully removed user from organization." +msgstr "Successfully removed user from organization." + +#: src/affiliation/mutations/request-org-affiliation.js:231 +msgid "Successfully requested invite to organization, and sent notification email." +msgstr "Successfully requested invite to organization, and sent notification email." + +#: src/affiliation/mutations/invite-user-to-org.js:170 +msgid "Successfully sent invitation to service, and organization email." +msgstr "Successfully sent invitation to service, and organization email." + +#: src/user/mutations/sign-out.js:25 +msgid "Successfully signed out." +msgstr "Successfully signed out." + +#. placeholder {0}: org.slug +#. placeholder {1}: requestedUser.userName +#: src/affiliation/mutations/transfer-org-ownership.js:188 +msgid "Successfully transferred org: {0} ownership to user: {1}" +msgstr "Successfully transferred org: {0} ownership to user: {1}" + +#. placeholder {0}: org.slug +#. placeholder {1}: tags.join(', ') +#: src/domain/mutations/update-domains-by-domain-ids.js:179 +#: src/domain/mutations/update-domains-by-filters.js:310 +msgid "Successfully updated {domainCount} domain(s) in {0} with {1}." +msgstr "Successfully updated {domainCount} domain(s) in {0} with {1}." + +#. placeholder {0}: currentOrg.slug +#: src/organization/mutations/verify-organization.js:90 +msgid "Successfully verified organization: {0}." +msgstr "Successfully verified organization: {0}." + +#: src/user/mutations/verify-phone-number.js:91 +msgid "Successfully verified phone number, and set TFA send method to text." +msgstr "Successfully verified phone number, and set TFA send method to text." + +#: src/tags/mutations/update-tag.js:113 +#~ msgid "Tag label already in use, please choose another and try again." +#~ msgstr "Tag label already in use, please choose another and try again." + +#: src/tags/mutations/create-tag.js:94 +#: src/tags/mutations/create-tag.js:148 +#: src/tags/mutations/update-tag.js:161 +msgid "Tag label already in use. Please try again with a different label." +msgstr "Tag label already in use. Please try again with a different label." + +#: src/user/mutations/authenticate.js:66 +msgid "Token value incorrect, please sign in again." +msgstr "Token value incorrect, please sign in again." + +#: src/user/mutations/sign-in.js:78 +msgid "Too many failed login attempts, please reset your password, and try again." +msgstr "Too many failed login attempts, please reset your password, and try again." + +#: src/user/mutations/complete-tour.js:73 +msgid "Tour completion confirmed successfully" +msgstr "Tour completion confirmed successfully" + +#: src/user/mutations/verify-phone-number.js:51 +msgid "Two factor code is incorrect. Please try again." +msgstr "Two factor code is incorrect. Please try again." + +#: src/user/mutations/verify-phone-number.js:41 +msgid "Two factor code length is incorrect. Please try again." +msgstr "Two factor code length is incorrect. Please try again." + +#: src/affiliation/mutations/leave-organization.js:71 +#: src/affiliation/mutations/leave-organization.js:79 +msgid "Unable leave organization. Please try again." +msgstr "Unable leave organization. Please try again." + +#: src/domain/mutations/add-organizations-domains.js:108 +msgid "Unable to add domains in unknown organization." +msgstr "Unable to add domains in unknown organization." + +#: src/organization/data-source.js:179 +#: src/organization/data-source.js:194 +#: src/organization/data-source.js:209 +#: src/organization/data-source.js:217 +msgid "Unable to archive organization. Please try again." +msgstr "Unable to archive organization. Please try again." + +#: src/organization/mutations/archive-organization.js:52 +msgid "Unable to archive unknown organization." +msgstr "Unable to archive unknown organization." + +#: src/user/mutations/authenticate.js:79 +#: src/user/mutations/authenticate.js:121 +#: src/user/mutations/authenticate.js:146 +#: src/user/mutations/authenticate.js:155 +msgid "Unable to authenticate. Please try again." +msgstr "Unable to authenticate. Please try again." + +#: src/auth/checks/check-permission.js:26 +#: src/auth/checks/check-permission.js:64 +#: src/auth/checks/check-super-admin.js:20 +#: src/auth/checks/check-super-admin.js:30 +#: src/auth/loaders/load-permission-by-org-id.js:27 +#: src/auth/loaders/load-permission-by-org-id.js:73 +msgid "Unable to check permission. Please try again." +msgstr "Unable to check permission. Please try again." + +#: src/user/mutations/close-account.js:160 +msgid "Unable to close account of an undefined user." +msgstr "Unable to close account of an undefined user." + +#: src/user/mutations/close-account.js:49 +#: src/user/mutations/close-account.js:65 +#: src/user/mutations/close-account.js:73 +#: src/user/mutations/close-account.js:183 +#: src/user/mutations/close-account.js:199 +#: src/user/mutations/close-account.js:207 +msgid "Unable to close account. Please try again." +msgstr "Unable to close account. Please try again." + +#: src/user/mutations/complete-tour.js:39 +#: src/user/mutations/complete-tour.js:64 +msgid "Unable to confirm completion of the tour. Please try again." +msgstr "Unable to confirm completion of the tour. Please try again." + +#: src/domain/mutations/create-domain.js:116 +msgid "Unable to create domain in unknown organization." +msgstr "Unable to create domain in unknown organization." + +#: src/domain/mutations/create-domain.js:199 +msgid "Unable to create domain, organization has already claimed it." +msgstr "Unable to create domain, organization has already claimed it." + +#: src/domain/mutations/create-domain.js:181 +#: src/domain/mutations/create-domain.js:189 +#: src/domain/mutations/create-domain.js:221 +#: src/domain/mutations/create-domain.js:230 +#: src/domain/mutations/create-domain.js:250 +#: src/domain/mutations/create-domain.js:258 +msgid "Unable to create domain. Please try again." +msgstr "Unable to create domain. Please try again." + +#: src/domain/mutations/add-organizations-domains.js:277 +msgid "Unable to create domains. Please try again." +msgstr "Unable to create domains. Please try again." + +#: src/organization/data-source.js:58 +#: src/organization/data-source.js:76 +#: src/organization/data-source.js:84 +msgid "Unable to create organization. Please try again." +msgstr "Unable to create organization. Please try again." + +#: src/tags/mutations/create-tag.js:119 +msgid "Unable to create tag in unknown organization." +msgstr "Unable to create tag in unknown organization." + +#: src/tags/mutations/create-tag.js:108 +msgid "Unable to create tag, tagId already in use." +msgstr "Unable to create tag, tagId already in use." + +#: src/tags/data-source.js:58 +#: src/tags/data-source.js:65 +msgid "Unable to create tag. Please try again." +msgstr "Unable to create tag. Please try again." + +#: src/domain/mutations/request-discovery.js:86 +msgid "Unable to discover domains for unknown organization." +msgstr "Unable to discover domains for unknown organization." + +#: src/user/mutations/dismiss-message.js:39 +#: src/user/mutations/dismiss-message.js:66 +msgid "Unable to dismiss message. Please try again." +msgstr "Unable to dismiss message. Please try again." + +#: src/domain/mutations/request-scan.js:95 +#: src/domain/mutations/request-scan.js:109 +#: src/domain/mutations/request-scan.js:123 +#~ msgid "Unable to dispatch one time scan. Please try again." +#~ msgstr "Unable to dispatch one time scan. Please try again." + +#: src/organization/objects/organization.js:265 +msgid "Unable to export organization. Please try again." +msgstr "Unable to export organization. Please try again." + +#: src/domain/mutations/favourite-domain.js:82 +msgid "Unable to favourite domain, user has already favourited it." +msgstr "Unable to favourite domain, user has already favourited it." + +#: src/domain/mutations/favourite-domain.js:66 +#: src/domain/mutations/favourite-domain.js:74 +#: src/domain/mutations/favourite-domain.js:103 +#: src/domain/mutations/favourite-domain.js:111 +#: src/domain/mutations/unfavourite-domain.js:68 +#: src/domain/mutations/unfavourite-domain.js:76 +msgid "Unable to favourite domain. Please try again." +msgstr "Unable to favourite domain. Please try again." + +#: src/domain/mutations/favourite-domain.js:51 +msgid "Unable to favourite unknown domain." +msgstr "Unable to favourite unknown domain." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:34 +#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:48 +msgid "Unable to find Aggregate guidance tag(s). Please try again." +msgstr "Unable to find Aggregate guidance tag(s). Please try again." + +#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:34 +#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:48 +msgid "Unable to find DKIM guidance tag(s). Please try again." +msgstr "Unable to find DKIM guidance tag(s). Please try again." + +#: src/email-scan/loaders/load-dkim-result-by-key.js:20 +#: src/email-scan/loaders/load-dkim-result-by-key.js:34 +#~ msgid "Unable to find DKIM result(s). Please try again." +#~ msgstr "Unable to find DKIM result(s). Please try again." + +#: src/email-scan/loaders/load-dkim-by-key.js:19 +#: src/email-scan/loaders/load-dkim-by-key.js:31 +#~ msgid "Unable to find DKIM scan(s). Please try again." +#~ msgstr "Unable to find DKIM scan(s). Please try again." + +#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:34 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:48 +msgid "Unable to find DMARC guidance tag(s). Please try again." +msgstr "Unable to find DMARC guidance tag(s). Please try again." + +#: src/email-scan/loaders/load-dmarc-by-key.js:20 +#: src/email-scan/loaders/load-dmarc-by-key.js:34 +#~ msgid "Unable to find DMARC scan(s). Please try again." +#~ msgstr "Unable to find DMARC scan(s). Please try again." + +#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:37 +#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:51 +msgid "Unable to find DMARC summary data. Please try again." +msgstr "Unable to find DMARC summary data. Please try again." + +#: src/dns-scan/loaders/load-dns-by-key.js:20 +#: src/dns-scan/loaders/load-dns-by-key.js:34 +msgid "Unable to find DNS scan(s). Please try again." +msgstr "Unable to find DNS scan(s). Please try again." + +#: src/guidance-tag/loaders/load-guidance-tags.js:26 +#: src/guidance-tag/loaders/load-guidance-tags.js:36 +msgid "Unable to find guidance tag(s). Please try again." +msgstr "Unable to find guidance tag(s). Please try again." + +#: src/guidance-tag/loaders/load-https-guidance-tags.js:33 +#: src/guidance-tag/loaders/load-https-guidance-tags.js:47 +msgid "Unable to find HTTPS guidance tag(s). Please try again." +msgstr "Unable to find HTTPS guidance tag(s). Please try again." + +#: src/web-scan/loaders/load-https-by-key.js:19 +#~ msgid "Unable to find HTTPS scan(s). Please try again." +#~ msgstr "Unable to find HTTPS scan(s). Please try again." + +#: src/guidance-tag/loaders/load-spf-guidance-tags.js:28 +#: src/guidance-tag/loaders/load-spf-guidance-tags.js:42 +msgid "Unable to find SPF guidance tag(s). Please try again." +msgstr "Unable to find SPF guidance tag(s). Please try again." + +#: src/email-scan/loaders/load-spf-by-key.js:19 +#: src/email-scan/loaders/load-spf-by-key.js:31 +#~ msgid "Unable to find SPF scan(s). Please try again." +#~ msgstr "Unable to find SPF scan(s). Please try again." + +#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:28 +#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:42 +msgid "Unable to find SSL guidance tag(s). Please try again." +msgstr "Unable to find SSL guidance tag(s). Please try again." + +#: src/web-scan/loaders/load-ssl-by-key.js:18 +#: src/web-scan/loaders/load-ssl-by-key.js:30 +#~ msgid "Unable to find SSL scan(s). Please try again." +#~ msgstr "Unable to find SSL scan(s). Please try again." + +#: src/domain/queries/find-domain-by-domain.js:41 +msgid "Unable to find the requested domain." +msgstr "Unable to find the requested domain." + +#: src/affiliation/loaders/load-affiliation-by-key.js:22 +#: src/affiliation/loaders/load-affiliation-by-key.js:36 +msgid "Unable to find user affiliation(s). Please try again." +msgstr "Unable to find user affiliation(s). Please try again." + +#: src/verified-organizations/loaders/load-verified-organization-by-key.js:32 +#: src/verified-organizations/loaders/load-verified-organization-by-key.js:44 +#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:32 +#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:44 +msgid "Unable to find verified organization(s). Please try again." +msgstr "Unable to find verified organization(s). Please try again." + +#: src/domain/mutations/ignore-cve.js:64 +#: src/domain/mutations/ignore-cve.js:101 +#: src/domain/mutations/ignore-cve.js:123 +#: src/domain/mutations/ignore-cve.js:138 +#: src/domain/mutations/ignore-cve.js:149 +msgid "Unable to ignore CVE. Please try again." +msgstr "Unable to ignore CVE. Please try again." + +#: src/affiliation/mutations/invite-user-to-org.js:124 +#: src/affiliation/mutations/invite-user-to-org.js:190 +msgid "Unable to invite user to organization. Please try again." +msgstr "Unable to invite user to organization. Please try again." + +#: src/affiliation/mutations/invite-user-to-org.js:202 +msgid "Unable to invite user to organization. User is already affiliated with organization." +msgstr "Unable to invite user to organization. User is already affiliated with organization." + +#: src/affiliation/mutations/invite-user-to-org.js:84 +msgid "Unable to invite user to unknown organization." +msgstr "Unable to invite user to unknown organization." + +#: src/affiliation/mutations/invite-user-to-org.js:232 +#: src/affiliation/mutations/invite-user-to-org.js:253 +msgid "Unable to invite user. Please try again." +msgstr "Unable to invite user. Please try again." + +#: src/affiliation/mutations/invite-user-to-org.js:70 +msgid "Unable to invite yourself to an org." +msgstr "Unable to invite yourself to an org." + +#: src/affiliation/mutations/leave-organization.js:190 +#: src/affiliation/mutations/leave-organization.js:208 +#~ msgid "Unable to leave organization. Please try again." +#~ msgstr "Unable to leave organization. Please try again." + +#: src/affiliation/mutations/leave-organization.js:48 +msgid "Unable to leave undefined organization." +msgstr "Unable to leave undefined organization." + +#: src/additional-findings/loaders/load-additional-findings-by-domain-id.js:24 +#: src/additional-findings/loaders/load-additional-findings-by-domain-id.js:34 +msgid "Unable to load additional findings. Please try again." +msgstr "Unable to load additional findings. Please try again." + +#: src/auth/checks/check-user-belongs-to-org.js:20 +msgid "Unable to load affiliation information. Please try again." +msgstr "Unable to load affiliation information. Please try again." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:303 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:449 +msgid "Unable to load affiliation(s). Please try again." +msgstr "Unable to load affiliation(s). Please try again." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:254 +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:266 +msgid "Unable to load Aggregate guidance tag(s). Please try again." +msgstr "Unable to load Aggregate guidance tag(s). Please try again." + +#: src/organization/loaders/load-all-organization-domain-statuses.js:57 +#~ msgid "Unable to load all organization domain statuses. Please try again." +#~ msgstr "Unable to load all organization domain statuses. Please try again." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:50 +#: src/summaries/loaders/load-chart-summaries-by-period.js:60 +msgid "Unable to load chart summary data. Please try again." +msgstr "Unable to load chart summary data. Please try again." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:141 +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:153 +msgid "Unable to load DKIM failure data. Please try again." +msgstr "Unable to load DKIM failure data. Please try again." + +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:272 +msgid "Unable to load DKIM guidance tag(s). Please try again." +msgstr "Unable to load DKIM guidance tag(s). Please try again." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:258 +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:270 +#~ msgid "Unable to load DKIM result(s). Please try again." +#~ msgstr "Unable to load DKIM result(s). Please try again." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:279 +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:289 +#~ msgid "Unable to load DKIM scan(s). Please try again." +#~ msgstr "Unable to load DKIM scan(s). Please try again." + +#: src/summaries/queries/dkim-summary.js:12 +#~ msgid "Unable to load DKIM summary. Please try again." +#~ msgstr "Unable to load DKIM summary. Please try again." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:153 +msgid "Unable to load DMARC failure data. Please try again." +msgstr "Unable to load DMARC failure data. Please try again." + +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:272 +msgid "Unable to load DMARC guidance tag(s). Please try again." +msgstr "Unable to load DMARC guidance tag(s). Please try again." + +#: src/summaries/queries/dmarc-phase-summary.js:12 +#~ msgid "Unable to load DMARC phase summary. Please try again." +#~ msgstr "Unable to load DMARC phase summary. Please try again." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:319 +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:331 +#~ msgid "Unable to load DMARC scan(s). Please try again." +#~ msgstr "Unable to load DMARC scan(s). Please try again." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:449 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:459 +#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:20 +#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:32 +#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:20 +#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:32 +msgid "Unable to load DMARC summary data. Please try again." +msgstr "Unable to load DMARC summary data. Please try again." + +#: src/summaries/queries/dmarc-summary.js:12 +#~ msgid "Unable to load DMARC summary. Please try again." +#~ msgstr "Unable to load DMARC summary. Please try again." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:154 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:164 +msgid "Unable to load DNS scan(s). Please try again." +msgstr "Unable to load DNS scan(s). Please try again." + +#: src/domain/loaders/load-dkim-selectors-by-domain-id.js:18 +#: src/domain/loaders/load-dkim-selectors-by-domain-id.js:28 +msgid "Unable to load domain selector(s). Please try again." +msgstr "Unable to load domain selector(s). Please try again." + +#: src/domain/loaders/load-domain-by-domain.js:19 +#: src/domain/loaders/load-domain-by-domain.js:31 +#: src/domain/loaders/load-domain-by-key.js:19 +#: src/domain/loaders/load-domain-by-key.js:31 +msgid "Unable to load domain. Please try again." +msgstr "Unable to load domain. Please try again." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:402 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:412 +#: src/domain/loaders/load-domain-connections-by-user-id.js:469 +#: src/user/loaders/load-my-tracker-by-user-id.js:33 +msgid "Unable to load domain(s). Please try again." +msgstr "Unable to load domain(s). Please try again." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:140 +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:152 +msgid "Unable to load full pass data. Please try again." +msgstr "Unable to load full pass data. Please try again." + +#: src/guidance-tag/loaders/load-guidance-tags-connections.js:54 +#: src/guidance-tag/loaders/load-guidance-tags-connections.js:64 +msgid "Unable to load guidance tag(s). Please try again." +msgstr "Unable to load guidance tag(s). Please try again." + +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:272 +msgid "Unable to load HTTPS guidance tag(s). Please try again." +msgstr "Unable to load HTTPS guidance tag(s). Please try again." + +#: src/web-scan/loaders/load-https-by-key.js:33 +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:333 +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:345 +#~ msgid "Unable to load HTTPS scan(s). Please try again." +#~ msgstr "Unable to load HTTPS scan(s). Please try again." + +#: src/summaries/queries/https-summary.js:13 +#~ msgid "Unable to load HTTPS summary. Please try again." +#~ msgstr "Unable to load HTTPS summary. Please try again." + +#: src/audit-logs/loaders/load-audit-log-by-key.js:19 +#: src/audit-logs/loaders/load-audit-log-by-key.js:31 +msgid "Unable to load log. Please try again." +msgstr "Unable to load log. Please try again." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:318 +msgid "Unable to load log(s). Please try again." +msgstr "Unable to load log(s). Please try again." + +#: src/summaries/queries/mail-summary.js:12 +#~ msgid "Unable to load mail summary. Please try again." +#~ msgstr "Unable to load mail summary. Please try again." + +#: src/additional-findings/loaders/load-top-25-reports.js:29 +#: src/organization/loaders/load-all-organization-domain-statuses.js:164 +#: src/organization/loaders/load-organization-domain-statuses.js:172 +msgid "Unable to load organization domain statuses. Please try again." +msgstr "Unable to load organization domain statuses. Please try again." + +#: src/organization/loaders/load-organization-names-by-id.js:19 +#: src/organization/loaders/load-organization-names-by-id.js:29 +msgid "Unable to load organization names. Please try again." +msgstr "Unable to load organization names. Please try again." + +#: src/organization/loaders/load-organization-summaries-by-period.js:56 +#: src/organization/loaders/load-organization-summaries-by-period.js:66 +msgid "Unable to load organization summary data. Please try again." +msgstr "Unable to load organization summary data. Please try again." + +#: src/organization/data-source.js:117 +#: src/organization/data-source.js:124 +#: src/organization/data-source.js:144 +#: src/organization/data-source.js:152 +msgid "Unable to load organization. Please try again." +msgstr "Unable to load organization. Please try again." + +#: src/organization/loaders/load-organization-by-key.js:31 +#: src/organization/loaders/load-organization-by-key.js:41 +#: src/organization/loaders/load-organization-by-slug.js:34 +#: src/organization/loaders/load-organization-by-slug.js:45 +#: src/organization/loaders/load-organization-connections-by-domain-id.js:508 +#: src/organization/loaders/load-organization-connections-by-domain-id.js:518 +#: src/organization/loaders/load-organization-connections-by-user-id.js:544 +#: src/organization/loaders/load-organization-connections-by-user-id.js:554 +msgid "Unable to load organization(s). Please try again." +msgstr "Unable to load organization(s). Please try again." + +#: src/auth/checks/check-org-owner.js:19 +#: src/auth/checks/check-org-owner.js:27 +#: src/auth/loaders/load-org-owner-by-org-id.js:23 +#: src/auth/loaders/load-org-owner-by-org-id.js:33 +msgid "Unable to load owner information. Please try again." +msgstr "Unable to load owner information. Please try again." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:140 +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:152 +msgid "Unable to load SPF failure data. Please try again." +msgstr "Unable to load SPF failure data. Please try again." + +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:272 +msgid "Unable to load SPF guidance tag(s). Please try again." +msgstr "Unable to load SPF guidance tag(s). Please try again." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:306 +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:316 +#~ msgid "Unable to load SPF scan(s). Please try again." +#~ msgstr "Unable to load SPF scan(s). Please try again." + +#: src/summaries/queries/spf-summary.js:12 +#~ msgid "Unable to load SPF summary. Please try again." +#~ msgstr "Unable to load SPF summary. Please try again." + +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 +msgid "Unable to load SSL guidance tag(s). Please try again." +msgstr "Unable to load SSL guidance tag(s). Please try again." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:380 +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:390 +#~ msgid "Unable to load SSL scan(s). Please try again." +#~ msgstr "Unable to load SSL scan(s). Please try again." + +#: src/summaries/queries/ssl-summary.js:12 +#~ msgid "Unable to load SSL summary. Please try again." +#~ msgstr "Unable to load SSL summary. Please try again." + +#: src/summaries/loaders/load-chart-summary-by-key.js:17 +#: src/summaries/loaders/load-chart-summary-by-key.js:25 +#~ msgid "Unable to load summary. Please try again." +#~ msgstr "Unable to load summary. Please try again." + +#: src/tags/loaders/load-all-tags.js:36 +#: src/tags/loaders/load-all-tags.js:44 +#: src/tags/loaders/load-tag-by-tag-id.js:25 +#: src/tags/loaders/load-tag-by-tag-id.js:35 +#: src/tags/loaders/load-tags-by-org.js:36 +#: src/tags/loaders/load-tags-by-org.js:44 +msgid "Unable to load tag(s). Please try again." +msgstr "Unable to load tag(s). Please try again." + +#: src/domain/loaders/load-domain-tags-by-org-id.js:37 +#~ msgid "Unable to load tags(s). Please try again." +#~ msgstr "Unable to load tags(s). Please try again." + +#: src/user/loaders/load-user-by-key.js:19 +#: src/user/loaders/load-user-by-key.js:31 +#: src/user/loaders/load-user-by-username.js:19 +#: src/user/loaders/load-user-by-username.js:31 +#: src/user/loaders/load-user-connections-by-user-id.js:346 +msgid "Unable to load user(s). Please try again." +msgstr "Unable to load user(s). Please try again." + +#: src/verified-domains/loaders/load-verified-domain-by-domain.js:24 +#: src/verified-domains/loaders/load-verified-domain-by-domain.js:38 +#: src/verified-domains/loaders/load-verified-domain-by-key.js:24 +#: src/verified-domains/loaders/load-verified-domain-by-key.js:38 +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:306 +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:318 +#: src/verified-domains/loaders/load-verified-domain-connections.js:312 +#: src/verified-domains/loaders/load-verified-domain-connections.js:324 +msgid "Unable to load verified domain(s). Please try again." +msgstr "Unable to load verified domain(s). Please try again." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:423 +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:435 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:422 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:434 +msgid "Unable to load verified organization(s). Please try again." +msgstr "Unable to load verified organization(s). Please try again." + +#: src/dmarc-summaries/loaders/load-all-verified-rua-domains.js:27 +msgid "Unable to load verified rua domains. Please try again." +msgstr "Unable to load verified rua domains. Please try again." + +#: src/summaries/queries/web-connections-summary.js:12 +#~ msgid "Unable to load web connections summary. Please try again." +#~ msgstr "Unable to load web connections summary. Please try again." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:169 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:179 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:9 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:25 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:35 +msgid "Unable to load web scan(s). Please try again." +msgstr "Unable to load web scan(s). Please try again." + +#: src/summaries/queries/web-summary.js:13 +#~ msgid "Unable to load web summary. Please try again." +#~ msgstr "Unable to load web summary. Please try again." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:293 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:437 +msgid "Unable to query affiliation(s). Please try again." +msgstr "Unable to query affiliation(s). Please try again." + +#: src/domain/loaders/load-domain-connections-by-user-id.js:459 +#: src/user/loaders/load-my-tracker-by-user-id.js:23 +msgid "Unable to query domain(s). Please try again." +msgstr "Unable to query domain(s). Please try again." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:308 +msgid "Unable to query log(s). Please try again." +msgstr "Unable to query log(s). Please try again." + +#: src/user/loaders/load-user-connections-by-user-id.js:336 +msgid "Unable to query user(s). Please try again." +msgstr "Unable to query user(s). Please try again." + +#: src/user/mutations/refresh-tokens.js:47 +#: src/user/mutations/refresh-tokens.js:59 +#: src/user/mutations/refresh-tokens.js:72 +#: src/user/mutations/refresh-tokens.js:85 +#: src/user/mutations/refresh-tokens.js:95 +#: src/user/mutations/refresh-tokens.js:123 +#: src/user/mutations/refresh-tokens.js:131 +msgid "Unable to refresh tokens, please sign in." +msgstr "Unable to refresh tokens, please sign in." + +#: src/affiliation/mutations/remove-user-from-org.js:106 +msgid "Unable to remove a user that already does not belong to this organization." +msgstr "Unable to remove a user that already does not belong to this organization." + +#: src/domain/mutations/remove-domain.js:82 +msgid "Unable to remove domain from unknown organization." +msgstr "Unable to remove domain from unknown organization." + +#: src/domain/mutations/remove-domain.js:138 +msgid "Unable to remove domain. Domain is not part of organization." +msgstr "Unable to remove domain. Domain is not part of organization." + +#: src/domain/mutations/remove-domain.js:125 +#: src/domain/mutations/remove-domain.js:154 +#: src/domain/mutations/remove-domain.js:188 +#: src/domain/mutations/remove-domain.js:208 +#: src/domain/mutations/remove-domain.js:237 +#: src/domain/mutations/remove-domain.js:256 +#: src/domain/mutations/remove-domain.js:274 +#: src/domain/mutations/remove-domain.js:291 +#: src/domain/mutations/remove-domain.js:309 +#: src/domain/mutations/remove-domain.js:333 +#: src/domain/mutations/remove-domain.js:345 +msgid "Unable to remove domain. Please try again." +msgstr "Unable to remove domain. Please try again." + +#: src/domain/mutations/remove-organizations-domains.js:91 +msgid "Unable to remove domains from unknown organization." +msgstr "Unable to remove domains from unknown organization." + +#: src/organization/data-source.js:278 +#: src/organization/data-source.js:307 +#: src/organization/data-source.js:321 +#: src/organization/data-source.js:350 +#: src/organization/data-source.js:374 +#: src/organization/data-source.js:390 +#: src/organization/data-source.js:405 +#: src/organization/data-source.js:419 +#: src/organization/data-source.js:448 +#: src/organization/data-source.js:490 +#: src/organization/data-source.js:498 +msgid "Unable to remove organization. Please try again." +msgstr "Unable to remove organization. Please try again." + +#: src/user/mutations/remove-phone-number.js:51 +#: src/user/mutations/remove-phone-number.js:59 +msgid "Unable to remove phone number. Please try again." +msgstr "Unable to remove phone number. Please try again." + +#: src/domain/mutations/remove-domain.js:67 +msgid "Unable to remove unknown domain." +msgstr "Unable to remove unknown domain." + +#: src/organization/mutations/remove-organization.js:52 +msgid "Unable to remove unknown organization." +msgstr "Unable to remove unknown organization." + +#: src/affiliation/mutations/remove-user-from-org.js:79 +msgid "Unable to remove unknown user from organization." +msgstr "Unable to remove unknown user from organization." + +#: src/affiliation/mutations/remove-user-from-org.js:74 +#~ msgid "Unable to remove user from organization." +#~ msgstr "Unable to remove user from organization." + +#: src/affiliation/mutations/remove-user-from-org.js:96 +#: src/affiliation/mutations/remove-user-from-org.js:117 +#: src/affiliation/mutations/remove-user-from-org.js:164 +#: src/affiliation/mutations/remove-user-from-org.js:174 +msgid "Unable to remove user from this organization. Please try again." +msgstr "Unable to remove user from this organization. Please try again." + +#: src/affiliation/mutations/remove-user-from-org.js:63 +msgid "Unable to remove user from unknown organization." +msgstr "Unable to remove user from unknown organization." + +#: src/domain/mutations/request-scan.js:120 +msgid "Unable to request a one time scan on a domain that already has a pending scan." +msgstr "Unable to request a one time scan on a domain that already has a pending scan." + +#: src/domain/mutations/request-scan.js:55 +msgid "Unable to request a one time scan on an unknown domain." +msgstr "Unable to request a one time scan on an unknown domain." + +#: src/domain/mutations/request-scan.js:128 +msgid "Unable to request a one time scan. Please try again." +msgstr "Unable to request a one time scan. Please try again." + +#: src/domain/mutations/request-discovery.js:63 +msgid "Unable to request a subdomain discovery scan on an invalid domain." +msgstr "Unable to request a subdomain discovery scan on an invalid domain." + +#: src/domain/mutations/request-discovery.js:73 +msgid "Unable to request a subdomain discovery scan on an unknown domain." +msgstr "Unable to request a subdomain discovery scan on an unknown domain." + +#: src/affiliation/mutations/request-org-affiliation.js:98 +msgid "Unable to request invite to organization with which you are already affiliated." +msgstr "Unable to request invite to organization with which you are already affiliated." + +#: src/affiliation/mutations/request-org-affiliation.js:88 +msgid "Unable to request invite to organization with which you have already requested to join." +msgstr "Unable to request invite to organization with which you have already requested to join." + +#: src/affiliation/mutations/request-org-affiliation.js:59 +msgid "Unable to request invite to unknown organization." +msgstr "Unable to request invite to unknown organization." + +#: src/affiliation/mutations/request-org-affiliation.js:75 +#: src/affiliation/mutations/request-org-affiliation.js:124 +#: src/affiliation/mutations/request-org-affiliation.js:141 +#: src/affiliation/mutations/request-org-affiliation.js:152 +#: src/affiliation/mutations/request-org-affiliation.js:167 +#: src/affiliation/mutations/request-org-affiliation.js:198 +msgid "Unable to request invite. Please try again." +msgstr "Unable to request invite. Please try again." + +#: src/user/mutations/reset-password.js:86 +#~ msgid "Unable to reset password. Please request a new email." +#~ msgstr "Unable to reset password. Please request a new email." + +#: src/user/mutations/reset-password.js:76 +#: src/user/mutations/reset-password.js:125 +#: src/user/mutations/reset-password.js:133 +msgid "Unable to reset password. Please try again." +msgstr "Unable to reset password. Please try again." + +#: src/domain/objects/domain.js:274 +#: src/domain/objects/domain.js:309 +msgid "Unable to retrieve DMARC report information for: {domain}" +msgstr "Unable to retrieve DMARC report information for: {domain}" + +#: src/dmarc-summaries/loaders/load-start-date-from-period.js:33 +#: src/dmarc-summaries/loaders/load-start-date-from-period.js:48 +msgid "Unable to select DMARC report(s) for this period and year." +msgstr "Unable to select DMARC report(s) for this period and year." + +#: src/notify/notify-send-authenticate-email.js:19 +msgid "Unable to send authentication email. Please try again." +msgstr "Unable to send authentication email. Please try again." + +#: src/notify/notify-send-authenticate-text-msg.js:32 +msgid "Unable to send authentication text message. Please try again." +msgstr "Unable to send authentication text message. Please try again." + +#: src/notify/notify-send-org-invite-create-account.js:19 +#: src/notify/notify-send-org-invite-email.js:18 +msgid "Unable to send org invite email. Please try again." +msgstr "Unable to send org invite email. Please try again." + +#: src/notify/notify-send-invite-request-email.js:19 +msgid "Unable to send org invite request email. Please try again." +msgstr "Unable to send org invite request email. Please try again." + +#: src/notify/notify-send-password-reset-email.js:18 +msgid "Unable to send password reset email. Please try again." +msgstr "Unable to send password reset email. Please try again." + +#: src/notify/notify-send-role-change-email.js:21 +#~ msgid "Unable to send role update email. Please try again." +#~ msgstr "Unable to send role update email. Please try again." + +#: src/notify/notify-send-tfa-text-msg.js:30 +#~ msgid "Unable to send two factor authentication message. Please try again." +#~ msgstr "Unable to send two factor authentication message. Please try again." + +#: src/notify/notify-send-updated-username-email.js:18 +#: src/user/mutations/verify-account.js:102 +msgid "Unable to send updated username email. Please try again." +msgstr "Unable to send updated username email. Please try again." + +#: src/notify/notify-send-verification-email.js:18 +msgid "Unable to send verification email. Please try again." +msgstr "Unable to send verification email. Please try again." + +#: src/user/mutations/set-phone-number.js:97 +#: src/user/mutations/set-phone-number.js:105 +msgid "Unable to set phone number, please try again." +msgstr "Unable to set phone number, please try again." + +#: src/user/mutations/sign-in.js:98 +#: src/user/mutations/sign-in.js:132 +#: src/user/mutations/sign-in.js:140 +#: src/user/mutations/sign-in.js:198 +#: src/user/mutations/sign-in.js:206 +#: src/user/mutations/sign-in.js:270 +#: src/user/mutations/sign-in.js:278 +msgid "Unable to sign in, please try again." +msgstr "Unable to sign in, please try again." + +#: src/user/mutations/sign-up.js:197 +#: src/user/mutations/sign-up.js:208 +msgid "Unable to sign up, please contact org admin for a new invite." +msgstr "Unable to sign up, please contact org admin for a new invite." + +#: src/user/mutations/sign-up.js:168 +#: src/user/mutations/sign-up.js:177 +#: src/user/mutations/sign-up.js:229 +#: src/user/mutations/sign-up.js:238 +msgid "Unable to sign up. Please try again." +msgstr "Unable to sign up. Please try again." + +#: src/domain/mutations/unignore-cve.js:64 +#: src/domain/mutations/unignore-cve.js:101 +#: src/domain/mutations/unignore-cve.js:123 +#: src/domain/mutations/unignore-cve.js:138 +#: src/domain/mutations/unignore-cve.js:149 +msgid "Unable to stop ignoring CVE. Please try again." +msgstr "Unable to stop ignoring CVE. Please try again." + +#: src/affiliation/mutations/transfer-org-ownership.js:106 +#: src/affiliation/mutations/transfer-org-ownership.js:146 +#: src/affiliation/mutations/transfer-org-ownership.js:169 +#: src/affiliation/mutations/transfer-org-ownership.js:180 +msgid "Unable to transfer organization ownership. Please try again." +msgstr "Unable to transfer organization ownership. Please try again." + +#: src/affiliation/mutations/transfer-org-ownership.js:68 +#~ msgid "Unable to transfer ownership of a verified organization." +#~ msgstr "Unable to transfer ownership of a verified organization." + +#: src/affiliation/mutations/transfer-org-ownership.js:89 +msgid "Unable to transfer ownership of an org to an undefined user." +msgstr "Unable to transfer ownership of an org to an undefined user." + +#: src/affiliation/mutations/transfer-org-ownership.js:59 +msgid "Unable to transfer ownership of undefined organization." +msgstr "Unable to transfer ownership of undefined organization." + +#: src/affiliation/mutations/transfer-org-ownership.js:118 +msgid "Unable to transfer ownership to a user outside the org. Please invite the user and try again." +msgstr "Unable to transfer ownership to a user outside the org. Please invite the user and try again." + +#: src/user/mutations/verify-phone-number.js:72 +#: src/user/mutations/verify-phone-number.js:80 +msgid "Unable to two factor authenticate. Please try again." +msgstr "Unable to two factor authenticate. Please try again." + +#: src/domain/mutations/unfavourite-domain.js:84 +msgid "Unable to unfavourite domain, domain is not favourited." +msgstr "Unable to unfavourite domain, domain is not favourited." + +#: src/domain/mutations/unfavourite-domain.js:111 +#: src/domain/mutations/unfavourite-domain.js:119 +msgid "Unable to unfavourite domain. Please try again." +msgstr "Unable to unfavourite domain. Please try again." + +#: src/domain/mutations/unfavourite-domain.js:53 +msgid "Unable to unfavourite unknown domain." +msgstr "Unable to unfavourite unknown domain." + +#: src/domain/mutations/unignore-cve.js:62 +#: src/domain/mutations/unignore-cve.js:98 +#: src/domain/mutations/unignore-cve.js:108 +#~ msgid "Unable to unignore CVE. Please try again." +#~ msgstr "Unable to unignore CVE. Please try again." + +#: src/domain/mutations/update-domain.js:261 +msgid "Unable to update domain edge. Please try again." +msgstr "Unable to update domain edge. Please try again." + +#: src/domain/mutations/update-domain.js:137 +msgid "Unable to update domain in an unknown org." +msgstr "Unable to update domain in an unknown org." + +#: src/domain/mutations/update-domain.js:178 +msgid "Unable to update domain that does not belong to the given organization." +msgstr "Unable to update domain that does not belong to the given organization." + +#: src/domain/mutations/update-domain.js:168 +#: src/domain/mutations/update-domain.js:218 +#: src/domain/mutations/update-domain.js:272 +msgid "Unable to update domain. Please try again." +msgstr "Unable to update domain. Please try again." + +#: src/domain/mutations/update-domains-by-domain-ids.js:68 +#: src/domain/mutations/update-domains-by-filters.js:76 +msgid "Unable to update domains in unknown organization." +msgstr "Unable to update domains in unknown organization." + +#: src/domain/mutations/update-domains-by-filters.js:229 +#: src/domain/mutations/update-domains-by-filters.js:237 +#: src/domain/mutations/update-domains-by-filters.js:244 +msgid "Unable to update domains. Please try again." +msgstr "Unable to update domains. Please try again." + +#: src/organization/data-source.js:101 +msgid "Unable to update organization. Please try again." +msgstr "Unable to update organization. Please try again." + +#: src/user/mutations/update-user-password.js:52 +msgid "Unable to update password, current password does not match. Please try again." +msgstr "Unable to update password, current password does not match. Please try again." + +#: src/user/mutations/update-user-password.js:62 +msgid "Unable to update password, new passwords do not match. Please try again." +msgstr "Unable to update password, new passwords do not match. Please try again." + +#: src/user/mutations/update-user-password.js:74 +msgid "Unable to update password, passwords do not match requirements. Please try again." +msgstr "Unable to update password, passwords do not match requirements. Please try again." + +#: src/user/mutations/update-user-password.js:95 +#: src/user/mutations/update-user-password.js:103 +msgid "Unable to update password. Please try again." +msgstr "Unable to update password. Please try again." + +#: src/user/mutations/update-user-profile.js:154 +#: src/user/mutations/update-user-profile.js:162 +msgid "Unable to update profile. Please try again." +msgstr "Unable to update profile. Please try again." + +#: src/affiliation/mutations/update-user-role.js:97 +msgid "Unable to update role: organization unknown." +msgstr "Unable to update role: organization unknown." + +#: src/affiliation/mutations/update-user-role.js:140 +msgid "Unable to update role: user does not belong to organization." +msgstr "Unable to update role: user does not belong to organization." + +#: src/affiliation/mutations/update-user-role.js:83 +msgid "Unable to update role: user unknown." +msgstr "Unable to update role: user unknown." + +#: src/tags/mutations/update-tag.js:113 +msgid "Unable to update tag in unknown organization." +msgstr "Unable to update tag in unknown organization." + +#: src/tags/mutations/update-tag.js:103 +msgid "Unable to update tag, orgId is invalid." +msgstr "Unable to update tag, orgId is invalid." + +#: src/tags/mutations/update-tag.js:127 +#~ msgid "Unable to update tag, tagId already in use." +#~ msgstr "Unable to update tag, tagId already in use." + +#: src/tags/data-source.js:30 +#: src/tags/data-source.js:36 +#: src/tags/data-source.js:92 +#: src/tags/data-source.js:101 +msgid "Unable to update tag. Please try again." +msgstr "Unable to update tag. Please try again." + +#: src/domain/mutations/update-domain.js:123 +msgid "Unable to update unknown domain." +msgstr "Unable to update unknown domain." + +#: src/organization/mutations/update-organization.js:137 +msgid "Unable to update unknown organization." +msgstr "Unable to update unknown organization." + +#: src/tags/mutations/update-tag.js:87 +msgid "Unable to update unknown tag." +msgstr "Unable to update unknown tag." + +#: src/affiliation/mutations/update-user-role.js:130 +#: src/affiliation/mutations/update-user-role.js:152 +#: src/affiliation/mutations/update-user-role.js:196 +#: src/affiliation/mutations/update-user-role.js:206 +#: src/affiliation/mutations/update-user-role.js:217 +msgid "Unable to update user's role. Please try again." +msgstr "Unable to update user's role. Please try again." + +#: src/affiliation/mutations/update-user-role.js:69 +msgid "Unable to update your own role." +msgstr "Unable to update your own role." + +#: src/user/mutations/verify-account.js:51 +#: src/user/mutations/verify-account.js:63 +#: src/user/mutations/verify-account.js:77 +msgid "Unable to verify account. Please request a new email." +msgstr "Unable to verify account. Please request a new email." + +#: src/user/mutations/verify-account.js:128 +#: src/user/mutations/verify-account.js:136 +msgid "Unable to verify account. Please try again." +msgstr "Unable to verify account. Please try again." + +#: src/user/queries/is-user-super-admin.js:24 +msgid "Unable to verify if user is a super admin, please try again." +msgstr "Unable to verify if user is a super admin, please try again." + +#: src/user/mutations/update-user-profile.js:99 +#: src/user/queries/is-user-admin.js:49 +msgid "Unable to verify if user is an admin, please try again." +msgstr "Unable to verify if user is an admin, please try again." + +#: src/organization/data-source.js:237 +#: src/organization/data-source.js:252 +#: src/organization/data-source.js:260 +msgid "Unable to verify organization. Please try again." +msgstr "Unable to verify organization. Please try again." + +#: src/organization/mutations/verify-organization.js:50 +msgid "Unable to verify unknown organization." +msgstr "Unable to verify unknown organization." + +#: src/user/queries/find-user-by-username.js:41 +msgid "User could not be queried." +msgstr "User could not be queried." + +#: src/user/mutations/sign-up.js:79 +msgid "User is trying to register for a non-production environment." +msgstr "User is trying to register for a non-production environment." + +#: src/affiliation/mutations/update-user-role.js:253 +msgid "User role was updated successfully." +msgstr "User role was updated successfully." + +#: src/user/mutations/update-user-profile.js:80 +#: src/user/mutations/verify-account.js:88 +msgid "Username not available, please try another." +msgstr "Username not available, please try another." + +#: src/auth/guards/tfa-required.js:15 +msgid "Verification error. Please activate multi-factor authentication to access content." +msgstr "Verification error. Please activate multi-factor authentication to access content." + +#: src/auth/guards/verified-required.js:15 +msgid "Verification error. Please verify your account via email to access content." +msgstr "Verification error. Please verify your account via email to access content." + +#: src/additional-findings/loaders/load-additional-findings-by-domain-id.js:8 +msgid "You must provide a `domainId` to retrieve a domain's additional findings." +msgstr "You must provide a `domainId` to retrieve a domain's additional findings." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:82 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:161 +msgid "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:114 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." +#~ msgstr "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:89 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." +#~ msgstr "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:138 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." +#~ msgstr "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:162 +msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:141 +#: src/domain/loaders/load-domain-connections-by-user-id.js:166 +msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `Domain` connection." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:85 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:89 +msgid "You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:147 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." +#~ msgstr "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:100 +msgid "You must provide a `first` or `last` value to properly paginate the `Log` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `Log` connection." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:166 +#: src/organization/loaders/load-organization-connections-by-user-id.js:178 +msgid "You must provide a `first` or `last` value to properly paginate the `Organization` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `Organization` connection." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:133 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `SPF` connection." +#~ msgstr "You must provide a `first` or `last` value to properly paginate the `SPF` connection." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:171 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `SSL` connection." +#~ msgstr "You must provide a `first` or `last` value to properly paginate the `SSL` connection." + +#: src/user/loaders/load-user-connections-by-user-id.js:106 +msgid "You must provide a `first` or `last` value to properly paginate the `User` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `User` connection." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:117 +#: src/verified-domains/loaders/load-verified-domain-connections.js:117 +msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:167 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:165 +msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." +msgstr "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:16 +msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection." +msgstr "You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection." + +#: src/dns-scan/loaders/load-mx-record-diff-by-domain-id.js:16 +#~ msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `MXRecord` connection." +#~ msgstr "You must provide a `limit` value in the range of 1-100 to properly paginate the `MXRecord` connection." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:16 +msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection." +msgstr "You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:9 +msgid "You must provide a `limit` value to properly paginate the `DNS` connection." +msgstr "You must provide a `limit` value to properly paginate the `DNS` connection." + +#: src/dns-scan/loaders/load-mx-record-diff-by-domain-id.js:9 +#~ msgid "You must provide a `limit` value to properly paginate the `MXRecord` connection." +#~ msgstr "You must provide a `limit` value to properly paginate the `MXRecord` connection." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:9 +msgid "You must provide a `limit` value to properly paginate the `web` connection." +msgstr "You must provide a `limit` value to properly paginate the `web` connection." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:8 +#~ msgid "You must provide a `period` value to access the `ChartSummaries` connection." +#~ msgstr "You must provide a `period` value to access the `ChartSummaries` connection." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:12 +msgid "You must provide a `period` value to access the `DmarcSummaries` connection." +msgstr "You must provide a `period` value to access the `DmarcSummaries` connection." + +#: src/organization/loaders/load-organization-summaries-by-period.js:10 +#~ msgid "You must provide a `period` value to access the `OrganizationSummaries` connection." +#~ msgstr "You must provide a `period` value to access the `OrganizationSummaries` connection." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:43 +#~ msgid "You must provide a `year` value to access the `ChartSummaries` connection." +#~ msgstr "You must provide a `year` value to access the `ChartSummaries` connection." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:18 +msgid "You must provide a `year` value to access the `DmarcSummaries` connection." +msgstr "You must provide a `year` value to access the `DmarcSummaries` connection." + +#: src/organization/loaders/load-organization-summaries-by-period.js:46 +#~ msgid "You must provide a `year` value to access the `OrganizationSummaries` connection." +#~ msgstr "You must provide a `year` value to access the `OrganizationSummaries` connection." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:30 +msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection." +msgstr "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection." + +#: src/dns-scan/loaders/load-mx-record-diff-by-domain-id.js:30 +#~ msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `MXRecord` connection." +#~ msgstr "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `MXRecord` connection." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:30 +msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." +msgstr "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:13 +#~ msgid "You must provide both `startDate` and `endDate` values to access the `ChartSummaries` connection." +#~ msgstr "You must provide both `startDate` and `endDate` values to access the `ChartSummaries` connection." + +#: src/organization/loaders/load-organization-summaries-by-period.js:13 +#~ msgid "You must provide both `startDate` and `endDate` values to access the `OrganizationSummaries` connection." +#~ msgstr "You must provide both `startDate` and `endDate` values to access the `OrganizationSummaries` connection." diff --git a/api/src/locale/fr/messages.js b/api/src/locale/fr/messages.js new file mode 100644 index 0000000000..1ef81295f7 --- /dev/null +++ b/api/src/locale/fr/messages.js @@ -0,0 +1 @@ +/*eslint-disable*/module.exports={messages:JSON.parse("{\"Eoi1qW\":[\"`\",[\"argSet\"],\"` doit être de type `number` et non `\",[\"typeSet\"],\"`.\"],\"uPlrHl\":[\"`\",[\"argSet\"],\"` sur la connexion `Affiliation` ne peut être inférieur à zéro.\"],\"EtaVdR\":[\"`\",[\"argSet\"],\"` sur la connexion `DKIM` ne peut être inférieur à zéro.\"],\"2npn9d\":[\"`\",[\"argSet\"],\"` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro.\"],\"mpaL2O\":[\"`\",[\"argSet\"],\"` sur la connexion `DKIMResults` ne peut être inférieur à zéro.\"],\"2vz4+a\":[\"`\",[\"argSet\"],\"` sur la connexion `DMARC` ne peut être inférieur à zéro.\"],\"ujwWhu\":[\"`\",[\"argSet\"],\"` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro.\"],\"HbNnNq\":[\"`\",[\"argSet\"],\"` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro.\"],\"eSKAjw\":[\"`\",[\"argSet\"],\"` sur la connexion `Domain` ne peut être inférieur à zéro.\"],\"R2ityD\":[\"`\",[\"argSet\"],\"` sur la connexion `FullPassTable` ne peut être inférieur à zéro.\"],\"dqZo9M\":[\"`\",[\"argSet\"],\"` sur la connexion `GuidanceTag` ne peut être inférieure à zéro.\"],\"YXZolK\":[\"`\",[\"argSet\"],\"` sur la connexion `HTTPS` ne peut être inférieur à zéro.\"],\"UACahy\":[\"`\",[\"argSet\"],\"` sur la connexion `Log` ne peut être inférieur à zéro.\"],\"nkUPC9\":[\"`\",[\"argSet\"],\"` sur la connexion `Organization` ne peut être inférieure à zéro.\"],\"Jkk2ml\":[\"`\",[\"argSet\"],\"` sur la connexion `SPF` ne peut être inférieure à zéro.\"],\"Rfl7ba\":[\"`\",[\"argSet\"],\"` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro.\"],\"w6BMWR\":[\"`\",[\"argSet\"],\"` sur la connexion `SSL` ne peut être inférieur à zéro.\"],\"kk1tk/\":[\"`\",[\"argSet\"],\"` sur la connexion `User` ne peut être inférieure à zéro.\"],\"6ZERsG\":[\"`\",[\"argSet\"],\"` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.\"],\"plfOI4\":[\"`\",[\"argSet\"],\"` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.\"],\"w3MeQ2\":\"Erreur d'authentification. Veuillez vous connecter.\",\"9QA26B\":\"Il n'est pas possible de demander des résultats supplémentaires sans autorisation.\",\"OEzMB0\":\"Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus.\",\"2K0+0j\":\"Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus.\",\"Z2VjXv\":\"Impossible d'interroger les résultats de l'analyse DNS sans autorisation.\",\"SsrKFI\":\"Impossible d'interroger les sélecteurs de domaine sans autorisation.\",\"/fzcPY\":\"Impossible d'interroger les résultats de l'analyse web sans autorisation.\",\"uWrncA\":\"CVE est déjà ignoré pour ce domaine.\",\"messFf\":\"Le CVE n'est pas ignoré dans ce domaine.\",\"K9BF0u\":\"Courriel déjà utilisé.\",\"kr40ez\":\"Erreur lors de la demande d'analyse. Veuillez réessayer.\",\"xty1cI\":\"Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception.\",\"n3yyu8\":\"Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.\",\"YPojZ6\":\"Code TFA incorrect. Veuillez vous reconnecter.\",\"q/Mmq8\":\"La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.\",\"MXwSlX\":\"Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.\",\"1YEhLV\":\"Jeton invalide, veuillez vous connecter.\",\"ydzoW2\":\"Message rejeté avec succès\",\"6Ylhna\":\"Les nouveaux mots de passe ne correspondent pas.\",\"BwHoFc\":\"Aucune organisation avec le slug fourni n'a pu être trouvée.\",\"MnkZ16\":\"Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.\",\"L9hADR\":\"Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.\",\"uYSCJj\":\"L'organisation a déjà été vérifiée.\",\"p20l3d\":\"Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.\",\"SVGjT0\":\"Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.\",\"dndFWA\":\"Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.\",\"Drhmz3\":\"Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté.\",\"85YUUC\":\"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.\",\"ELjtwQ\":\"Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté.\",\"fh3YJO\":\"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.\",\"x4tq+e\":\"Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.\",\"594X4Y\":\"Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté.\",\"X0oiNk\":\"Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté.\",\"k0nHGG\":\"Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.\",\"qvXcAG\":\"Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté.\",\"iC7wND\":\"Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.\",\"oiwNsK\":\"Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.\",\"UQKJcw\":\"Passer à la fois `first` et `last` pour paginer la connexion `Log` n'est pas supporté.\",\"9QRMXj\":\"Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté.\",\"y0439x\":\"Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.\",\"kp3U0m\":\"Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté.\",\"o4LzIu\":\"Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.\",\"eMCdJf\":\"Passer à la fois `first` et `last` pour paginer la connexion `User` n'est pas supporté.\",\"9lvjn+\":\"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté.\",\"diyob+\":\"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté.\",\"t0Yhq7\":\"Le mot de passe ne répond pas aux exigences.\",\"JweT52\":\"Le mot de passe a été réinitialisé avec succès.\",\"U7wcn8\":\"Le mot de passe a été mis à jour avec succès.\",\"fDGOiR\":\"Les mots de passe ne correspondent pas.\",\"VJ90eS\":\"Erreur de vérification des permissions. Impossible de demander des informations sur le domaine.\",\"smjfcW\":\"Permission refusée : Impossible de récupérer l'organisation spécifiée.\",\"6b0Uzo\":\"Permission refusée : L'authentification multifactorielle est requise pour les comptes admin.\",\"d7fLWy\":\"Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété.\",\"xBt+I5\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines.\",\"qqp3YV\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.\",\"9ty0BN\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des domaines.\",\"XLwFgy\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.\",\"AJ7T8X\":\"Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.\",\"9MP0sV\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.\",\"9/MaeX\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.\",\"gJAEM/\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.\",\"b+aSK2\":\"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.\",\"MkIId8\":\"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.\",\"zCsJJ+\":\"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines.\",\"zXXF5h\":\"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine.\",\"wI7ogy\":\"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.\",\"MU2+6C\":\"Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.\",\"WO5Xev\":\"Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'organisation de l'archivage.\",\"uf+M40\":\"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.\",\"Sf3vFY\":\"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.\",\"kJ+fSd\":\"Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'analyse de ce domaine.\",\"c11gmL\":\"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.\",\"Kwq7PZ\":\"Erreur de permission, pas d'administrateur pour cet utilisateur.\",\"ht7RB9\":\"Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.\",\"S3055l\":\"Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données.\",\"qj6rps\":\"Le numéro de téléphone a été supprimé avec succès.\",\"uEAFen\":\"Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification.\",\"LbgZEx\":\"Veuillez fournir un commentaire lorsque vous ajoutez un domaine externe.\",\"yu5LUi\":\"Le profil a été mis à jour avec succès.\",\"22PG3h\":[\"La demande de \",[\"amount\"],\" enregistrements sur la connexion `DKIM` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"lEZ2x7\":[\"La demande de \",[\"amount\"],\" enregistrements sur la connexion `DKIMResults` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"3kc6YF\":[\"La demande de \",[\"amount\"],\" enregistrements sur la connexion `DMARC` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"wxwMqQ\":[\"La demande de \",[\"amount\"],\" enregistrements sur la connexion `HTTPS` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"qAmoqx\":[\"La demande de \",[\"amount\"],\" enregistrements sur la connexion `SPF` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"JD82Qj\":[\"La demande de \",[\"amount\"],\" enregistrements sur la connexion `SSL` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"ocE6aN\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `Affiliation` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"VF35kt\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `DkimFailureTable` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"fPydkO\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `DkimFailureTable` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"zOGaYb\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `DmarcSummaries` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"oSdPzK\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `Domain` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"sfbRxl\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `FullPassTable` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"9ECkIl\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `GuidanceTag` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"u4JPdU\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `Log` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"UuKFug\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `Organization` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"ytIe4o\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `SpfFailureTable` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"5vNdM3\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `User` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"SfCdEm\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `VerifiedDomain` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"4Vvv6j\":[\"La demande d'enregistrements `\",[\"amount\"],\"` sur la connexion `VerifiedOrganization` dépasse la limite `\",[\"argSet\"],\"` de 100 enregistrements.\"],\"FPrtxO\":[\"Ajouté avec succès le(s) domaine(s) \",[\"domainCount\"],\" à \",[\"0\"],\".\"],\"aNKZ2s\":[\"Ajouté avec succès les domaines \",[\"domainCount\"],\" à \",[\"0\"],\".\"],\"6kD2ow\":[\"Organisation archivée avec succès : \",[\"0\"],\".\"],\"du9Xou\":\"Le compte a été fermé avec succès.\",\"/XMSy3\":\"Un seul balayage a été effectué avec succès.\",\"EknPJ9\":\"L'analyse de découverte du sous-domaine a été effectuée avec succès.\",\"WnhHoM\":\"Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.\",\"ZekZu1\":\"Envoi d'un courriel à un compte vérifié.\",\"v/sjus\":\"L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.\",\"NOS/E2\":[\"L'organisation a été quittée avec succès: \",[\"0\"]],\"Mb7h0p\":[\"Supprimé avec succès le(s) domaine(s) \",[\"domainCount\"],\" de \",[\"0\"],\".\"],\"whT9qL\":[\"Suppression réussie des domaines \",[\"domainCount\"],\" de \",[\"0\"],\".\"],\"KJoIYL\":[\"A réussi à supprimer le domaine : \",[\"0\"],\" de \",[\"1\"],\".\"],\"B3bhOU\":[\"A réussi à supprimer le domaine : \",[\"0\"],\" des favoris.\"],\"6UbMHy\":[\"A réussi à supprimer l'organisation : \",[\"0\"],\".\"],\"m3IqsS\":\"L'utilisateur a été retiré de l'organisation avec succès.\",\"whspKB\":\"La demande d'invitation à l'organisation a été effectuée avec succès et un courriel de notification a été envoyé.\",\"hsTfau\":\"Envoi réussi de l'invitation au service, et de l'email de l'organisation.\",\"92cunG\":\"J'ai réussi à me déconnecter.\",\"UhOnUX\":[\"A réussi à transférer la propriété de org: \",[\"0\"],\" à l'utilisateur: \",[\"1\"]],\"QLcXYT\":\"Envoi réussi de l'invitation au service, et de l'email de l'organisation.\",\"0UHOyT\":\"Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte.\",\"G+Imw/\":\"La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.\",\"bTMB63\":\"Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer.\",\"KPk8On\":\"Confirmation de l'achèvement du voyage\",\"ghwok7\":\"Le code à deux facteurs est incorrect. Veuillez réessayer.\",\"to2nZU\":\"La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.\",\"4G8XnK\":\"Impossible de quitter l'organisation. Veuillez réessayer.\",\"VtK4v3\":\"Impossible d'ajouter des domaines dans une organisation inconnue.\",\"5c9XSu\":\"Impossible d'archiver l'organisation. Veuillez réessayer.\",\"d0IZcx\":\"Impossible d'archiver une organisation inconnue.\",\"iGBFKx\":\"Impossible de s'authentifier. Veuillez réessayer.\",\"hGYodY\":\"Impossible de vérifier l'autorisation. Veuillez réessayer.\",\"JBqBmK\":\"Impossible de fermer le compte d'un utilisateur non défini.\",\"ASvI35\":\"Impossible de fermer le compte. Veuillez réessayer.\",\"lYCFPq\":\"Impossible de confirmer l'achèvement de la visite. Veuillez réessayer.\",\"JH9/Jq\":\"Impossible de créer un domaine dans une organisation inconnue.\",\"TUFqYc\":\"Impossible de créer le domaine, l'organisation l'a déjà réclamé.\",\"PnTl0X\":\"Impossible de créer un domaine. Veuillez réessayer.\",\"VkUlo7\":\"Impossible de créer des domaines. Veuillez réessayer.\",\"Bk0F/3\":\"Impossible de créer une organisation. Veuillez réessayer.\",\"ggS9AU\":\"Impossible de découvrir les domaines d'une organisation inconnue.\",\"HLFEUv\":\"Impossible de rejeter le message. Veuillez réessayer.\",\"U8WJkK\":\"Impossible d'envoyer un scan unique. Veuillez réessayer.\",\"sX31Qs\":\"Impossible d'exporter l'organisation. Veuillez réessayer.\",\"QDd6b7\":\"Impossible de favoriser le domaine, l'utilisateur l'a déjà favorisé.\",\"+4vwFL\":\"Impossible d'accéder au domaine favori. Veuillez réessayer.\",\"WNzPnw\":\"Impossible de favoriser le domaine inconnu.\",\"lPIuFw\":\"Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.\",\"q4hBrf\":\"Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.\",\"BC7dKY\":\"Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.\",\"C5fP4H\":\"Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.\",\"GH27F7\":\"Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.\",\"ngV4gO\":\"Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.\",\"GtHb3j\":\"Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.\",\"ULGdf/\":\"Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer.\",\"kzp3/l\":\"Impossible de trouver les étiquettes d'orientation. Veuillez réessayer.\",\"lvuEwe\":\"Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.\",\"eEnZq2\":\"Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.\",\"8UI/ez\":\"Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.\",\"EJbuMu\":\"Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.\",\"jy/9PC\":\"Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.\",\"xsn+ev\":\"Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.\",\"itNwLs\":\"Impossible de trouver le domaine demandé.\",\"AzNyVV\":\"Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.\",\"usyHv4\":\"Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.\",\"J0A2iO\":\"Impossible d'ignorer le CVE. Veuillez réessayer.\",\"QlRbEs\":\"Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer.\",\"Y3CReu\":\"Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation.\",\"4YaHho\":\"Impossible d'inviter un utilisateur à une organisation inconnue.\",\"6DMjZ8\":\"Impossible d'inviter un utilisateur. Veuillez réessayer.\",\"mFUvni\":\"Impossible de s'inviter à un org.\",\"RuV5QP\":\"Impossible de quitter l'organisation. Veuillez réessayer.\",\"pAAJtC\":\"Impossible de quitter une organisation non définie.\",\"NRzPen\":\"Impossible de charger des résultats supplémentaires. Veuillez réessayer.\",\"Q2fyq2\":\"Impossible de charger les informations d'affiliation. Veuillez réessayer.\",\"dLE6X6\":\"Impossible de charger l'affiliation (s). Veuillez réessayer.\",\"tEuM0Y\":\"Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.\",\"z6WNrg\":\"Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer.\",\"AS+XYm\":\"Impossible de charger les données du résumé du graphique. Veuillez réessayer.\",\"VZSEkv\":\"Impossible de charger les données d'échec DKIM. Veuillez réessayer.\",\"iHCZPz\":\"Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.\",\"VYgxhl\":\"Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.\",\"XmHD0n\":\"Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.\",\"H/foxs\":\"Impossible de charger le résumé DKIM. Veuillez réessayer.\",\"DZfpKQ\":\"Impossible de charger les données d'échec DMARC. Veuillez réessayer.\",\"IWleX/\":\"Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.\",\"9KOsEh\":\"Impossible de charger le résumé DMARC. Veuillez réessayer.\",\"eNPs30\":\"Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.\",\"wKC+/J\":\"Impossible de charger les données de synthèse DMARC. Veuillez réessayer.\",\"PF8L8T\":\"Impossible de charger le résumé DMARC. Veuillez réessayer.\",\"zv2kI4\":\"Impossible de charger le(s) scan(s) DNS. Veuillez réessayer.\",\"+mdFBt\":\"Impossible de charger le(s) sélecteur(s) de domaine. Veuillez réessayer.\",\"hpeR8m\":\"Impossible de charger le domaine. Veuillez réessayer.\",\"f/tKr7\":\"Impossible de charger le(s) domaine(s). Veuillez réessayer.\",\"aHKeg4\":\"Impossible de charger les données complètes de la passe. Veuillez réessayer.\",\"3fphqj\":\"Impossible de charger le(s) tag(s) d'orientation. Veuillez réessayer.\",\"cCar8N\":\"Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.\",\"7A73DJ\":\"Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.\",\"BMKKFn\":\"Impossible de charger le résumé HTTPS. Veuillez réessayer.\",\"hZEouI\":\"Impossible de charger le journal. Veuillez réessayer.\",\"lSzsy0\":\"Impossible de charger le(s) journal(s). Veuillez réessayer.\",\"wrwKt2\":\"Impossible de charger le résumé du courrier. Veuillez réessayer.\",\"Fpm2AP\":\"Impossible de charger les statuts des domaines d'organisation. Veuillez réessayer.\",\"p6Lm32\":\"Impossible de charger les données de synthèse de l'organisation. Veuillez réessayer.\",\"8KT8ti\":\"Impossible de charger l'organisation (s). Veuillez réessayer.\",\"LvHBFL\":\"Impossible de charger les informations sur le propriétaire. Veuillez réessayer.\",\"sXOtBn\":\"Impossible de charger les données d'échec SPF. Veuillez réessayer.\",\"IQkbNl\":\"Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.\",\"FHxRJZ\":\"Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.\",\"ZsscmB\":\"Impossible de charger le résumé SPF. Veuillez réessayer.\",\"HI7CSw\":\"Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.\",\"tJYOji\":\"Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.\",\"PpwpCp\":\"Impossible de charger le résumé SSL. Veuillez réessayer.\",\"xzKtay\":\"Impossible de charger le résumé. Veuillez réessayer.\",\"y7ER1b\":\"Impossible de charger les balises. Veuillez réessayer.\",\"PzFOAc\":\"Impossible de charger le(s) utilisateur(s). Veuillez réessayer.\",\"Ph65og\":\"Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.\",\"QdFjoo\":\"Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.\",\"MjZQ9y\":\"Impossible de charger les domaines rua vérifiés. Veuillez réessayer.\",\"WMM6DC\":\"Impossible de charger le résumé des connexions web. Veuillez réessayer.\",\"szMW1T\":\"Impossible de charger le(s) scan(s) web. Veuillez réessayer.\",\"OEKyZJ\":\"Impossible de charger le résumé web. Veuillez réessayer.\",\"j4fHlS\":\"Impossible de demander l'affiliation (s). Veuillez réessayer.\",\"c0mbW+\":\"Impossible d'interroger le(s) domaine(s). Veuillez réessayer.\",\"7W4MNs\":\"Impossible d'interroger le(s) journal(s). Veuillez réessayer.\",\"098Gys\":\"Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer.\",\"KfROBu\":\"Impossible de rafraîchir les jetons, veuillez vous connecter.\",\"Hls3tJ\":\"Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation.\",\"ELijnP\":\"Impossible de supprimer le domaine d'une organisation inconnue.\",\"nuxrPH\":\"Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation.\",\"hHXji3\":\"Impossible de supprimer le domaine. Veuillez réessayer.\",\"iMzlxt\":\"Impossible de supprimer les domaines d'une organisation inconnue.\",\"X34ZOQ\":\"Impossible de supprimer l'organisation. Veuillez réessayer.\",\"Qf8OvV\":\"Impossible de supprimer le numéro de téléphone. Veuillez réessayer.\",\"GXX5w7\":\"Impossible de supprimer un domaine inconnu.\",\"pQ0cLH\":\"Impossible de supprimer une organisation inconnue.\",\"gWw+wU\":\"Impossible de supprimer un utilisateur inconnu de l'organisation.\",\"APaKFI\":\"Impossible de supprimer un utilisateur de l'organisation.\",\"v28SUT\":\"Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.\",\"qjq1fL\":\"Impossible de supprimer un utilisateur d'une organisation inconnue.\",\"Wu3IdK\":\"Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours.\",\"s79a/Q\":\"Impossible de demander un scan unique sur un domaine inconnu.\",\"BlpeBr\":\"Impossible de demander une analyse unique. Veuillez réessayer.\",\"YlWZ/S\":\"Impossible de demander un scan de découverte de sous-domaine sur un domaine invalide.\",\"/rRkGm\":\"Impossible de demander une analyse de découverte de sous-domaine sur un domaine inconnu.\",\"qTisBR\":\"Impossible de demander une invitation à une organisation à laquelle vous êtes déjà affilié.\",\"1+X6yw\":\"Impossible de demander une invitation à une organisation à laquelle vous avez déjà demandé à adhérer.\",\"Djnfqd\":\"Impossible de demander une invitation à une organisation inconnue.\",\"LlyhW+\":\"Impossible de demander une invitation. Veuillez réessayer.\",\"NQWBIw\":\"Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.\",\"dJ2lzx\":\"Impossible de réinitialiser le mot de passe. Veuillez réessayer.\",\"t2wESI\":[\"Impossible de récupérer les informations du rapport DMARC pour : \",[\"domain\"]],\"OTycVQ\":\"Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année.\",\"qT4MFW\":\"Impossible d'envoyer l'email d'authentification. Veuillez réessayer.\",\"LPodG6\":\"Impossible d'envoyer un message texte d'authentification. Veuillez réessayer.\",\"9TsNGX\":\"Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.\",\"zwqufn\":\"Impossible d'envoyer l'email de demande d'invitation à l'org. Veuillez réessayer.\",\"1rL9I7\":\"Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.\",\"svsSOK\":\"Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.\",\"ujyB0i\":\"Impossible d'envoyer l'email de mise à jour du nom d'utilisateur. Veuillez réessayer.\",\"02Keb+\":\"Impossible d'envoyer l'email de vérification. Veuillez réessayer.\",\"E4JeKk\":\"Impossible de définir le numéro de téléphone, veuillez réessayer.\",\"g3Svfo\":\"Impossible de se connecter, veuillez réessayer.\",\"VqhI6Q\":\"Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.\",\"2hEUmM\":\"Impossible de s'inscrire. Veuillez réessayer.\",\"7/HZU+\":\"Impossible d'arrêter d'ignorer le CVE. Veuillez réessayer.\",\"JLEqkA\":\"Impossible de transférer la propriété de l'organisation. Veuillez réessayer.\",\"U1j4ef\":\"Impossible de transférer la propriété d'une organisation vérifiée.\",\"c3WjG6\":\"Impossible de transférer la propriété d'un org à un utilisateur non défini.\",\"TMjnAo\":\"Impossible de transférer la propriété d'une organisation non définie.\",\"rzoBBf\":\"Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer.\",\"8TSaAb\":\"Impossible de s'authentifier par deux facteurs. Veuillez réessayer.\",\"FHUz/I\":\"Impossible de désactiver le domaine, le domaine n'est pas favorisé.\",\"F7RRVM\":\"Impossible de défavoriser le domaine. Veuillez réessayer.\",\"RgT2Fw\":\"Impossible de défavoriser un domaine inconnu.\",\"iM9uiT\":\"Unable to unignore CVE. Please try again.\",\"CmZgVA\":\"Impossible de mettre à jour le bord du domaine. Veuillez réessayer.\",\"gAMNI6\":\"Impossible de mettre à jour le domaine dans un org inconnu.\",\"JbEMFm\":\"Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.\",\"jHfpyF\":\"Impossible de mettre à jour le domaine. Veuillez réessayer.\",\"0tE9dW\":\"Impossible de mettre à jour l'organisation. Veuillez réessayer.\",\"4Dutt5\":\"Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer.\",\"fNY+xk\":\"Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer.\",\"TK5yIB\":\"Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer.\",\"2OQWjx\":\"Impossible de mettre à jour le mot de passe. Veuillez réessayer.\",\"+q5Rwg\":\"Impossible de mettre à jour le profil. Veuillez réessayer.\",\"Zqql23\":\"Impossible de mettre à jour le rôle : organisation inconnue.\",\"5g5CPf\":\"Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.\",\"ygexeT\":\"Impossible de mettre à jour le rôle : utilisateur inconnu.\",\"K5a9HQ\":\"Impossible de mettre à jour un domaine inconnu.\",\"4WmaEA\":\"Impossible de mettre à jour une organisation inconnue.\",\"13kdVN\":\"Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.\",\"xWavp5\":\"Impossible de mettre à jour votre propre rôle.\",\"Vs/Ux1\":\"Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.\",\"Z4+72s\":\"Impossible de vérifier le compte. Veuillez réessayer.\",\"Tqm26J\":\"Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer.\",\"KPWee0\":\"Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.\",\"apJs/U\":\"Impossible de vérifier l'organisation. Veuillez réessayer.\",\"x5sf5T\":\"Impossible de vérifier une organisation inconnue.\",\"sesHCz\":\"L'utilisateur n'a pas pu être interrogé.\",\"czGsbE\":\"L'utilisateur essaie de s'enregistrer dans un environnement de non-production.\",\"Tgpvqi\":\"Le rôle de l'utilisateur a été mis à jour avec succès.\",\"iuCjUb\":\"Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.\",\"V/PS9h\":\"Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu.\",\"7zQuGi\":\"Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.\",\"kMaiy6\":\"Vous devez fournir un `domainId` pour récupérer les résultats supplémentaires d'un domaine.\",\"p+DYLk\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`.\",\"c7s2Nw\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.\",\"SEEDiW\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`.\",\"wlbfWc\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.\",\"lu3LE3\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.\",\"pDllMl\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`.\",\"voJfyc\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`.\",\"c8Qbmn\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`.\",\"ZacidG\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`.\",\"mciz5o\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.\",\"87Zz6g\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.\",\"co0onA\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Log`.\",\"thP+Yz\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`.\",\"Xfjual\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.\",\"4s6W/Q\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`.\",\"EsdwHf\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.\",\"N+og2p\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `User`.\",\"EMHIWF\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`.\",\"RMtxoi\":\"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`.\",\"3Zdrst\":\"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `DNS`.\",\"DRToPU\":\"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `MXRecord`.\",\"NfXI/l\":\"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `web`.\",\"KAZHf6\":\"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`.\",\"uTt7kN\":\"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `MXRecord`.\",\"KyJdrc\":\"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`.\",\"TAJGTA\":\"Vous devez fournir une valeur `period` pour accéder à la connexion `ChartSummaries`.\",\"DWa4my\":\"Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.\",\"XZZ7Lv\":\"Vous devez fournir une valeur `period` pour accéder à la connexion `OrganizationSummaries`.\",\"oafMGl\":\"Vous devez fournir une valeur `year` pour accéder à la connexion `ChartSummaries`.\",\"jqcUEU\":\"Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.\",\"z4RkNn\":\"Vous devez fournir une valeur `year` pour accéder à la connexion `OrganizationSummaries`.\",\"JOssnw\":\"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`.\",\"cfpKpu\":\"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `MXRecord`.\",\"O++odO\":\"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`.\"}")}; \ No newline at end of file diff --git a/api/src/locale/fr/messages.po b/api/src/locale/fr/messages.po new file mode 100644 index 0000000000..d1672a6160 --- /dev/null +++ b/api/src/locale/fr/messages.po @@ -0,0 +1,1905 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: \n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:121 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:131 +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:83 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:202 +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:178 +#: src/domain/loaders/load-domain-connections-by-user-id.js:205 +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:136 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:136 +#: src/organization/loaders/load-organization-connections-by-domain-id.js:203 +#: src/organization/loaders/load-organization-connections-by-user-id.js:213 +#: src/user/loaders/load-user-connections-by-user-id.js:145 +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:164 +#: src/verified-domains/loaders/load-verified-domain-connections.js:164 +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:214 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:212 +msgid "`{argSet}` must be of type `number` not `{typeSet}`." +msgstr "`{argSet}` doit être de type `number` et non `{typeSet}`." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:98 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:182 +msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `Affiliation` ne peut être inférieur à zéro." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:135 +#~ msgid "`{argSet}` on the `DKIM` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `DKIM` ne peut être inférieur à zéro." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:110 +#~ msgid "`{argSet}` on the `DKIMResults` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `DKIMResults` ne peut être inférieur à zéro." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:160 +#~ msgid "`{argSet}` on the `DMARC` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `DMARC` ne peut être inférieur à zéro." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:179 +msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:157 +#: src/domain/loaders/load-domain-connections-by-user-id.js:182 +msgid "`{argSet}` on the `Domain` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `Domain` ne peut être inférieur à zéro." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `FullPassTable` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `FullPassTable` ne peut être inférieur à zéro." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:106 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:110 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:110 +msgid "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `GuidanceTag` ne peut être inférieure à zéro." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:168 +#~ msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `HTTPS` ne peut être inférieur à zéro." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:112 +msgid "`{argSet}` on the `Log` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `Log` ne peut être inférieur à zéro." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:182 +#: src/organization/loaders/load-organization-connections-by-user-id.js:192 +msgid "`{argSet}` on the `Organization` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `Organization` ne peut être inférieure à zéro." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:154 +#~ msgid "`{argSet}` on the `SPF` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `SPF` ne peut être inférieure à zéro." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:57 +msgid "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:192 +#~ msgid "`{argSet}` on the `SSL` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `SSL` ne peut être inférieur à zéro." + +#: src/user/loaders/load-user-connections-by-user-id.js:122 +msgid "`{argSet}` on the `User` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `User` ne peut être inférieure à zéro." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:138 +#: src/verified-domains/loaders/load-verified-domain-connections.js:138 +msgid "`{argSet}` on the `VerifiedDomain` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:188 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:186 +msgid "`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero." +msgstr "`{argSet}` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro." + +#: src/organization/objects/organization.js:240 +#: src/organization/queries/get-all-organization-domain-statuses.js:69 +msgid "Assess" +msgstr "Évaluez" + +#: src/auth/checks/check-permission.js:18 +#: src/auth/checks/check-permission.js:57 +#: src/auth/guards/user-required.js:10 +#: src/auth/guards/user-required.js:21 +#: src/auth/guards/user-required.js:28 +#: src/auth/loaders/load-permission-by-org-id.js:19 +#: src/auth/loaders/load-permission-by-org-id.js:63 +msgid "Authentication error. Please sign in." +msgstr "Erreur d'authentification. Veuillez vous connecter." + +#: src/domain/objects/domain.js:229 +msgid "Cannot query additional findings without permission." +msgstr "Il n'est pas possible de demander des résultats supplémentaires sans autorisation." + +#: src/organization/objects/organization.js:359 +msgid "Cannot query affiliations on organization without admin permission or higher." +msgstr "Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:224 +#: src/audit-logs/queries/find-audit-logs.js:53 +msgid "Cannot query audit logs on organization without admin permission or higher." +msgstr "Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus." + +#: src/domain/objects/domain.js:164 +msgid "Cannot query dns scan results without permission." +msgstr "Impossible d'interroger les résultats de l'analyse DNS sans autorisation." + +#: src/domain/objects/domain.js:65 +msgid "Cannot query domain selectors without permission." +msgstr "Impossible d'interroger les sélecteurs de domaine sans autorisation." + +#: src/domain/objects/domain.js:206 +msgid "Cannot query web scan results without permission." +msgstr "Impossible d'interroger les résultats de l'analyse web sans autorisation." + +#: src/domain/mutations/ignore-cve.js:77 +msgid "CVE is already ignored for this domain." +msgstr "CVE est déjà ignoré pour ce domaine." + +#: src/domain/mutations/unignore-cve.js:77 +msgid "CVE is not ignored for this domain." +msgstr "Le CVE n'est pas ignoré dans ce domaine." + +#: src/organization/objects/organization.js:242 +#: src/organization/queries/get-all-organization-domain-statuses.js:71 +msgid "Deploy" +msgstr "msgstr Déployer" + +#: src/user/mutations/sign-up.js:111 +msgid "Email already in use." +msgstr "Courriel déjà utilisé." + +#: src/organization/objects/organization.js:244 +#: src/organization/queries/get-all-organization-domain-statuses.js:73 +msgid "Enforce" +msgstr "Appliquer" + +#: src/domain/mutations/request-scan.js:90 +#: src/domain/mutations/request-scan.js:100 +msgid "Error while requesting scan. Please try again." +msgstr "Erreur lors de la demande d'analyse. Veuillez réessayer." + +#: src/user/mutations/send-password-reset.js:61 +msgid "If an account with this username is found, a password reset link will be found in your inbox." +msgstr "Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception." + +#: src/user/mutations/send-email-verification.js:60 +#~ msgid "If an account with this username is found, an email verification link will be found in your inbox." +#~ msgstr "Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception." + +#: src/user/mutations/authenticate.js:221 +#: src/user/mutations/authenticate.js:229 +#: src/user/mutations/authenticate.js:231 +msgid "Incorrect TFA code. Please sign in again." +msgstr "Code TFA incorrect. Veuillez vous reconnecter." + +#: src/user/mutations/reset-password.js:61 +msgid "Incorrect token value. Please request a new email." +msgstr "La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail." + +#: src/user/mutations/sign-in.js:68 +#: src/user/mutations/sign-in.js:285 +msgid "Incorrect username or password. Please try again." +msgstr "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer." + +#: src/auth/utils/verify-jwt.js:15 +msgid "Invalid token, please sign in." +msgstr "Jeton invalide, veuillez vous connecter." + +#: src/organization/objects/organization.js:246 +#: src/organization/queries/get-all-organization-domain-statuses.js:75 +msgid "Maintain" +msgstr "Maintenir" + +#: src/user/mutations/dismiss-message.js:75 +msgid "Message dismissed successfully" +msgstr "Message rejeté avec succès" + +#: src/user/mutations/reset-password.js:88 +msgid "New passwords do not match." +msgstr "Les nouveaux mots de passe ne correspondent pas." + +#: src/organization/queries/find-organization-by-slug.js:42 +#: src/user/queries/find-my-tracker.js:29 +msgid "No organization with the provided slug could be found." +msgstr "Aucune organisation avec le slug fourni n'a pu être trouvée." + +#: src/verified-domains/queries/find-verified-domain-by-domain.js:24 +msgid "No verified domain with the provided domain could be found." +msgstr "Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé." + +#: src/verified-organizations/queries/find-verified-organization-by-slug.js:24 +msgid "No verified organization with the provided slug could be found." +msgstr "Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée." + +#: src/organization/mutations/verify-organization.js:78 +msgid "Organization has already been verified." +msgstr "L'organisation a déjà été vérifiée." + +#: src/organization/mutations/update-organization.js:167 +msgid "Organization name already in use, please choose another and try again." +msgstr "Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer." + +#: src/organization/mutations/create-organization.js:85 +msgid "Organization name already in use. Please try again with a different name." +msgstr "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent." + +#: src/auth/checks/check-domain-ownership.js:29 +#: src/auth/checks/check-domain-ownership.js:39 +#: src/auth/checks/check-domain-ownership.js:65 +#: src/auth/checks/check-domain-ownership.js:74 +msgid "Ownership check error. Unable to request domain information." +msgstr "Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:89 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:170 +msgid "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:123 +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:159 +#~ msgid "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:98 +#~ msgid "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:147 +#~ msgid "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:170 +msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:148 +#: src/domain/loaders/load-domain-connections-by-user-id.js:173 +msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:94 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:98 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:98 +msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:156 +#~ msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:105 +msgid "Passing both `first` and `last` to paginate the `Log` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Log` n'est pas supporté." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:173 +#: src/organization/loaders/load-organization-connections-by-user-id.js:185 +msgid "Passing both `first` and `last` to paginate the `Organization` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:142 +#~ msgid "Passing both `first` and `last` to paginate the `SPF` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:45 +msgid "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:180 +#~ msgid "Passing both `first` and `last` to paginate the `SSL` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté." + +#: src/user/loaders/load-user-connections-by-user-id.js:113 +msgid "Passing both `first` and `last` to paginate the `User` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `User` n'est pas supporté." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:126 +#: src/verified-domains/loaders/load-verified-domain-connections.js:126 +msgid "Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:176 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:174 +msgid "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." +msgstr "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté." + +#: src/user/mutations/reset-password.js:100 +#: src/user/mutations/sign-up.js:89 +msgid "Password does not meet requirements." +msgstr "Le mot de passe ne répond pas aux exigences." + +#: src/user/mutations/reset-password.js:140 +msgid "Password was successfully reset." +msgstr "Le mot de passe a été réinitialisé avec succès." + +#: src/user/mutations/update-user-password.js:109 +msgid "Password was successfully updated." +msgstr "Le mot de passe a été mis à jour avec succès." + +#: src/user/mutations/sign-up.js:99 +msgid "Passwords do not match." +msgstr "Les mots de passe ne correspondent pas." + +#: src/auth/checks/check-domain-permission.js:22 +#: src/auth/checks/check-domain-permission.js:51 +#: src/auth/checks/check-domain-permission.js:61 +#: src/auth/loaders/load-domain-permission-by-domain-id.js:19 +#: src/auth/loaders/load-domain-permission-by-domain-id.js:51 +#: src/auth/loaders/load-domain-permission-by-domain-id.js:61 +msgid "Permission check error. Unable to request domain information." +msgstr "Erreur de vérification des permissions. Impossible de demander des informations sur le domaine." + +#: src/organization/queries/find-organization-by-slug.js:50 +#: src/organization/queries/find-organization-by-slug.js:52 +msgid "Permission Denied: Could not retrieve specified organization." +msgstr "Permission refusée : Impossible de récupérer l'organisation spécifiée." + +#: src/user/mutations/update-user-profile.js:109 +msgid "Permission Denied: Multi-factor authentication is required for admin accounts" +msgstr "Permission refusée : L'authentification multifactorielle est requise pour les comptes admin." + +#: src/affiliation/mutations/transfer-org-ownership.js:74 +msgid "Permission Denied: Please contact org owner to transfer ownership." +msgstr "Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété." + +#: src/domain/mutations/remove-organizations-domains.js:128 +msgid "Permission Denied: Please contact organization admin for help with archiving domains." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines." + +#: src/tags/mutations/create-tag.js:131 +msgid "Permission Denied: Please contact organization admin for help with creating tag." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la création d'un tag." + +#: src/domain/mutations/remove-domain.js:96 +msgid "Permission Denied: Please contact organization admin for help with removing domain." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine." + +#: src/domain/mutations/remove-organizations-domains.js:117 +msgid "Permission Denied: Please contact organization admin for help with removing domains." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des domaines." + +#: src/organization/mutations/remove-organization.js:67 +msgid "Permission Denied: Please contact organization admin for help with removing organization." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation." + +#: src/affiliation/mutations/remove-user-from-org.js:128 +#: src/affiliation/mutations/remove-user-from-org.js:140 +msgid "Permission Denied: Please contact organization admin for help with removing users." +msgstr "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." + +#: src/domain/mutations/update-domains-by-domain-ids.js:81 +#: src/domain/mutations/update-domains-by-filters.js:89 +msgid "Permission Denied: Please contact organization admin for help with updating domains." +msgstr "Autorisation refusée : veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant la mise à jour des domaines." + +#: src/organization/mutations/update-organization.js:152 +msgid "Permission Denied: Please contact organization admin for help with updating organization." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." + +#: src/tags/mutations/update-tag.js:125 +#: src/tags/mutations/update-tag.js:136 +msgid "Permission Denied: Please contact organization admin for help with updating tag." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour de la balise." + +#: src/affiliation/mutations/update-user-role.js:170 +#: src/affiliation/mutations/update-user-role.js:193 +#: src/affiliation/mutations/update-user-role.js:210 +#~ msgid "Permission Denied: Please contact organization admin for help with updating user roles." +#~ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs." + +#: src/affiliation/mutations/invite-user-to-org.js:99 +msgid "Permission Denied: Please contact organization admin for help with user invitations." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs." + +#: src/affiliation/mutations/update-user-role.js:112 +msgid "Permission Denied: Please contact organization admin for help with user role changes." +msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs." + +#: src/domain/mutations/create-domain.js:130 +msgid "Permission Denied: Please contact organization user for help with creating domain." +msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine." + +#: src/domain/mutations/add-organizations-domains.js:122 +msgid "Permission Denied: Please contact organization user for help with creating domains." +msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines." + +#: src/organization/objects/organization.js:111 +#~ msgid "Permission Denied: Please contact organization user for help with retrieving tags." +#~ msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide afin de récupérer les étiquettes." + +#: src/domain/queries/find-domain-by-domain.js:51 +#: src/organization/objects/organization.js:195 +msgid "Permission Denied: Please contact organization user for help with retrieving this domain." +msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine." + +#: src/domain/mutations/request-discovery.js:98 +#: src/domain/mutations/request-scan.js:66 +msgid "Permission Denied: Please contact organization user for help with scanning this domain." +msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine." + +#: src/domain/mutations/update-domain.js:151 +msgid "Permission Denied: Please contact organization user for help with updating this domain." +msgstr "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." + +#: src/organization/mutations/archive-organization.js:66 +msgid "Permission Denied: Please contact super admin for help with archiving organization." +msgstr "Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'organisation de l'archivage." + +#: src/domain/mutations/remove-domain.js:109 +#: src/domain/mutations/remove-organizations-domains.js:106 +msgid "Permission Denied: Please contact super admin for help with removing domain." +msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." + +#: src/organization/mutations/remove-organization.js:80 +msgid "Permission Denied: Please contact super admin for help with removing organization." +msgstr "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation." + +#: src/domain/mutations/request-scan.js:67 +#~ msgid "Permission Denied: Please contact super admin for help with scanning this domain." +#~ msgstr "Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'analyse de ce domaine." + +#: src/tags/mutations/update-tag.js:147 +msgid "Permission Denied: Please contact super admin for help with updating tag." +msgstr "Autorisation refusée : veuillez contacter l'administrateur principal pour obtenir de l'aide concernant la mise à jour de la balise." + +#: src/affiliation/mutations/invite-user-to-org.js:112 +msgid "Permission Denied: Please contact super admin for help with user invitations." +msgstr "Accès refusé : veuillez contacter l'administrateur principal pour obtenir de l'aide concernant les invitations d'utilisateurs." + +#: src/affiliation/mutations/update-user-role.js:167 +msgid "Permission Denied: Please contact super admin for help with user role changes." +msgstr "Accès refusé : veuillez contacter l'administrateur principal pour obtenir de l'aide concernant la modification des rôles d'utilisateur." + +#: src/organization/mutations/verify-organization.js:65 +msgid "Permission Denied: Please contact super admin for help with verifying this organization." +msgstr "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation." + +#: src/auth/checks/check-user-is-admin-for-user.js:20 +#: src/auth/checks/check-user-is-admin-for-user.js:30 +#: src/auth/checks/check-user-is-admin-for-user.js:63 +#: src/auth/checks/check-user-is-admin-for-user.js:73 +msgid "Permission error, not an admin for this user." +msgstr "Erreur de permission, pas d'administrateur pour cet utilisateur." + +#: src/user/mutations/close-account.js:148 +msgid "Permission error: Unable to close other user's account." +msgstr "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur." + +#: src/auth/guards/super-admin-required.js:11 +msgid "Permissions error. You do not have sufficient permissions to access this data." +msgstr "Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données." + +#: src/user/mutations/remove-phone-number.js:65 +msgid "Phone number has been successfully removed." +msgstr "Le numéro de téléphone a été supprimé avec succès." + +#: src/user/mutations/set-phone-number.js:118 +msgid "Phone number has been successfully set, you will receive a verification text message shortly." +msgstr "Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification." + +#: src/domain/mutations/create-domain.js:118 +#: src/domain/mutations/update-domain.js:124 +#~ msgid "Please provide a comment when adding an outside domain." +#~ msgstr "Veuillez fournir un commentaire lorsque vous ajoutez un domaine externe." + +#: src/user/mutations/update-user-profile.js:188 +msgid "Profile successfully updated." +msgstr "Le profil a été mis à jour avec succès." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:146 +#~ msgid "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `DKIM` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:121 +#~ msgid "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `DKIMResults` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:171 +#~ msgid "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `DMARC` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:179 +#~ msgid "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `HTTPS` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:165 +#~ msgid "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `SPF` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:203 +#~ msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `SSL` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:107 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:193 +msgid "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `Affiliation` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:188 +msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `DmarcSummaries` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:164 +#: src/domain/loaders/load-domain-connections-by-user-id.js:191 +msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `Domain` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `FullPassTable` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:117 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:121 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:121 +msgid "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `GuidanceTag` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:119 +msgid "Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `Log` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:189 +#: src/organization/loaders/load-organization-connections-by-user-id.js:199 +msgid "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `Organization` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:68 +msgid "Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `SpfFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/user/loaders/load-user-connections-by-user-id.js:131 +msgid "Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `User` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:149 +#: src/verified-domains/loaders/load-verified-domain-connections.js:149 +msgid "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `VerifiedDomain` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:199 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:197 +msgid "Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records." +msgstr "La demande d'enregistrements `{amount}` sur la connexion `VerifiedOrganization` dépasse la limite `{argSet}` de 100 enregistrements." + +#. placeholder {0}: org.slug +#: src/domain/mutations/add-organizations-domains.js:334 +msgid "Successfully added {domainCount} domain(s) to {0}." +msgstr "Ajouté avec succès le(s) domaine(s) {domainCount} à {0}." + +#: src/domain/mutations/add-organizations-domains.js:351 +#~ msgid "Successfully added {domainCount} domains to {0}." +#~ msgstr "Ajouté avec succès les domaines {domainCount} à {0}." + +#. placeholder {0}: organization.slug +#: src/organization/mutations/archive-organization.js:100 +msgid "Successfully archived organization: {0}." +msgstr "Organisation archivée avec succès : {0}." + +#: src/user/mutations/close-account.js:96 +#: src/user/mutations/close-account.js:230 +msgid "Successfully closed account." +msgstr "Le compte a été fermé avec succès." + +#: src/domain/mutations/request-scan.js:175 +msgid "Successfully dispatched one time scan." +msgstr "Un seul balayage a été effectué avec succès." + +#: src/domain/mutations/request-discovery.js:134 +msgid "Successfully dispatched subdomain discovery scan." +msgstr "L'analyse de découverte du sous-domaine a été effectuée avec succès." + +#: src/user/mutations/verify-account.js:97 +#~ msgid "Successfully email verified account, and set TFA send method to email." +#~ msgstr "Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email." + +#: src/user/mutations/verify-account.js:143 +msgid "Successfully email verified account." +msgstr "Envoi d'un courriel à un compte vérifié." + +#: src/affiliation/mutations/invite-user-to-org.js:282 +msgid "Successfully invited user to organization, and sent notification email." +msgstr "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé." + +#. placeholder {0}: org.slug +#: src/affiliation/mutations/leave-organization.js:86 +msgid "Successfully left organization: {0}" +msgstr "L'organisation a été quittée avec succès: {0}" + +#. placeholder {0}: org.slug +#: src/domain/mutations/remove-organizations-domains.js:477 +msgid "Successfully removed {domainCount} domain(s) from {0}." +msgstr "Supprimé avec succès le(s) domaine(s) {domainCount} de {0}." + +#: src/domain/mutations/remove-organizations-domains.js:530 +#~ msgid "Successfully removed {domainCount} domains from {0}." +#~ msgstr "Suppression réussie des domaines {domainCount} de {0}." + +#. placeholder {0}: domain.domain +#. placeholder {1}: org.slug +#: src/domain/mutations/remove-domain.js:373 +msgid "Successfully removed domain: {0} from {1}." +msgstr "A réussi à supprimer le domaine : {0} de {1}." + +#. placeholder {0}: domain.domain +#: src/domain/mutations/unfavourite-domain.js:126 +msgid "Successfully removed domain: {0} from favourites." +msgstr "A réussi à supprimer le domaine : {0} des favoris." + +#. placeholder {0}: organization.slug +#: src/organization/mutations/remove-organization.js:107 +msgid "Successfully removed organization: {0}." +msgstr "A réussi à supprimer l'organisation : {0}." + +#: src/affiliation/mutations/remove-user-from-org.js:201 +msgid "Successfully removed user from organization." +msgstr "L'utilisateur a été retiré de l'organisation avec succès." + +#: src/affiliation/mutations/request-org-affiliation.js:231 +msgid "Successfully requested invite to organization, and sent notification email." +msgstr "La demande d'invitation à l'organisation a été effectuée avec succès et un courriel de notification a été envoyé." + +#: src/affiliation/mutations/invite-user-to-org.js:170 +msgid "Successfully sent invitation to service, and organization email." +msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." + +#: src/user/mutations/sign-out.js:25 +msgid "Successfully signed out." +msgstr "J'ai réussi à me déconnecter." + +#. placeholder {0}: org.slug +#. placeholder {1}: requestedUser.userName +#: src/affiliation/mutations/transfer-org-ownership.js:188 +msgid "Successfully transferred org: {0} ownership to user: {1}" +msgstr "A réussi à transférer la propriété de org: {0} à l'utilisateur: {1}" + +#. placeholder {0}: org.slug +#. placeholder {1}: tags.join(', ') +#: src/domain/mutations/update-domains-by-domain-ids.js:179 +#: src/domain/mutations/update-domains-by-filters.js:310 +msgid "Successfully updated {domainCount} domain(s) in {0} with {1}." +msgstr "Mise à jour réussie de {domainCount} domaine(s) dans {0} avec {1}." + +#. placeholder {0}: currentOrg.slug +#: src/organization/mutations/verify-organization.js:90 +msgid "Successfully verified organization: {0}." +msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." + +#: src/user/mutations/verify-phone-number.js:91 +msgid "Successfully verified phone number, and set TFA send method to text." +msgstr "Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte." + +#: src/tags/mutations/update-tag.js:113 +#~ msgid "Tag label already in use, please choose another and try again." +#~ msgstr "L'étiquette est déjà utilisée, veuillez en choisir une autre et réessayer." + +#: src/tags/mutations/create-tag.js:94 +#: src/tags/mutations/create-tag.js:148 +#: src/tags/mutations/update-tag.js:161 +msgid "Tag label already in use. Please try again with a different label." +msgstr "L'étiquette est déjà utilisée. Veuillez réessayer avec une autre étiquette." + +#: src/user/mutations/authenticate.js:66 +msgid "Token value incorrect, please sign in again." +msgstr "La valeur du jeton est incorrecte, veuillez vous connecter à nouveau." + +#: src/user/mutations/sign-in.js:78 +msgid "Too many failed login attempts, please reset your password, and try again." +msgstr "Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer." + +#: src/user/mutations/complete-tour.js:73 +msgid "Tour completion confirmed successfully" +msgstr "Confirmation de l'achèvement du voyage" + +#: src/user/mutations/verify-phone-number.js:51 +msgid "Two factor code is incorrect. Please try again." +msgstr "Le code à deux facteurs est incorrect. Veuillez réessayer." + +#: src/user/mutations/verify-phone-number.js:41 +msgid "Two factor code length is incorrect. Please try again." +msgstr "La longueur du code à deux facteurs est incorrecte. Veuillez réessayer." + +#: src/affiliation/mutations/leave-organization.js:71 +#: src/affiliation/mutations/leave-organization.js:79 +msgid "Unable leave organization. Please try again." +msgstr "Impossible de quitter l'organisation. Veuillez réessayer." + +#: src/domain/mutations/add-organizations-domains.js:108 +msgid "Unable to add domains in unknown organization." +msgstr "Impossible d'ajouter des domaines dans une organisation inconnue." + +#: src/organization/data-source.js:179 +#: src/organization/data-source.js:194 +#: src/organization/data-source.js:209 +#: src/organization/data-source.js:217 +msgid "Unable to archive organization. Please try again." +msgstr "Impossible d'archiver l'organisation. Veuillez réessayer." + +#: src/organization/mutations/archive-organization.js:52 +msgid "Unable to archive unknown organization." +msgstr "Impossible d'archiver une organisation inconnue." + +#: src/user/mutations/authenticate.js:79 +#: src/user/mutations/authenticate.js:121 +#: src/user/mutations/authenticate.js:146 +#: src/user/mutations/authenticate.js:155 +msgid "Unable to authenticate. Please try again." +msgstr "Impossible de s'authentifier. Veuillez réessayer." + +#: src/auth/checks/check-permission.js:26 +#: src/auth/checks/check-permission.js:64 +#: src/auth/checks/check-super-admin.js:20 +#: src/auth/checks/check-super-admin.js:30 +#: src/auth/loaders/load-permission-by-org-id.js:27 +#: src/auth/loaders/load-permission-by-org-id.js:73 +msgid "Unable to check permission. Please try again." +msgstr "Impossible de vérifier l'autorisation. Veuillez réessayer." + +#: src/user/mutations/close-account.js:160 +msgid "Unable to close account of an undefined user." +msgstr "Impossible de fermer le compte d'un utilisateur non défini." + +#: src/user/mutations/close-account.js:49 +#: src/user/mutations/close-account.js:65 +#: src/user/mutations/close-account.js:73 +#: src/user/mutations/close-account.js:183 +#: src/user/mutations/close-account.js:199 +#: src/user/mutations/close-account.js:207 +msgid "Unable to close account. Please try again." +msgstr "Impossible de fermer le compte. Veuillez réessayer." + +#: src/user/mutations/complete-tour.js:39 +#: src/user/mutations/complete-tour.js:64 +msgid "Unable to confirm completion of the tour. Please try again." +msgstr "Impossible de confirmer l'achèvement de la visite. Veuillez réessayer." + +#: src/domain/mutations/create-domain.js:116 +msgid "Unable to create domain in unknown organization." +msgstr "Impossible de créer un domaine dans une organisation inconnue." + +#: src/domain/mutations/create-domain.js:199 +msgid "Unable to create domain, organization has already claimed it." +msgstr "Impossible de créer le domaine, l'organisation l'a déjà réclamé." + +#: src/domain/mutations/create-domain.js:181 +#: src/domain/mutations/create-domain.js:189 +#: src/domain/mutations/create-domain.js:221 +#: src/domain/mutations/create-domain.js:230 +#: src/domain/mutations/create-domain.js:250 +#: src/domain/mutations/create-domain.js:258 +msgid "Unable to create domain. Please try again." +msgstr "Impossible de créer un domaine. Veuillez réessayer." + +#: src/domain/mutations/add-organizations-domains.js:277 +msgid "Unable to create domains. Please try again." +msgstr "Impossible de créer des domaines. Veuillez réessayer." + +#: src/organization/data-source.js:58 +#: src/organization/data-source.js:76 +#: src/organization/data-source.js:84 +msgid "Unable to create organization. Please try again." +msgstr "Impossible de créer une organisation. Veuillez réessayer." + +#: src/tags/mutations/create-tag.js:119 +msgid "Unable to create tag in unknown organization." +msgstr "Impossible de créer une étiquette dans une organisation inconnue." + +#: src/tags/mutations/create-tag.js:108 +msgid "Unable to create tag, tagId already in use." +msgstr "Impossible de créer une balise, tagId déjà utilisé." + +#: src/tags/data-source.js:58 +#: src/tags/data-source.js:65 +msgid "Unable to create tag. Please try again." +msgstr "Impossible de créer une balise. Veuillez réessayer." + +#: src/domain/mutations/request-discovery.js:86 +msgid "Unable to discover domains for unknown organization." +msgstr "Impossible de découvrir les domaines d'une organisation inconnue." + +#: src/user/mutations/dismiss-message.js:39 +#: src/user/mutations/dismiss-message.js:66 +msgid "Unable to dismiss message. Please try again." +msgstr "Impossible de rejeter le message. Veuillez réessayer." + +#: src/domain/mutations/request-scan.js:95 +#: src/domain/mutations/request-scan.js:109 +#: src/domain/mutations/request-scan.js:123 +#~ msgid "Unable to dispatch one time scan. Please try again." +#~ msgstr "Impossible d'envoyer un scan unique. Veuillez réessayer." + +#: src/organization/objects/organization.js:265 +msgid "Unable to export organization. Please try again." +msgstr "Impossible d'exporter l'organisation. Veuillez réessayer." + +#: src/domain/mutations/favourite-domain.js:82 +msgid "Unable to favourite domain, user has already favourited it." +msgstr "Impossible de favoriser le domaine, l'utilisateur l'a déjà favorisé." + +#: src/domain/mutations/favourite-domain.js:66 +#: src/domain/mutations/favourite-domain.js:74 +#: src/domain/mutations/favourite-domain.js:103 +#: src/domain/mutations/favourite-domain.js:111 +#: src/domain/mutations/unfavourite-domain.js:68 +#: src/domain/mutations/unfavourite-domain.js:76 +msgid "Unable to favourite domain. Please try again." +msgstr "Impossible d'accéder au domaine favori. Veuillez réessayer." + +#: src/domain/mutations/favourite-domain.js:51 +msgid "Unable to favourite unknown domain." +msgstr "Impossible de favoriser le domaine inconnu." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:34 +#: src/guidance-tag/loaders/load-aggregate-guidance-tags.js:48 +msgid "Unable to find Aggregate guidance tag(s). Please try again." +msgstr "Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:34 +#: src/guidance-tag/loaders/load-dkim-guidance-tags.js:48 +msgid "Unable to find DKIM guidance tag(s). Please try again." +msgstr "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer." + +#: src/email-scan/loaders/load-dkim-result-by-key.js:20 +#: src/email-scan/loaders/load-dkim-result-by-key.js:34 +#~ msgid "Unable to find DKIM result(s). Please try again." +#~ msgstr "Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer." + +#: src/email-scan/loaders/load-dkim-by-key.js:19 +#: src/email-scan/loaders/load-dkim-by-key.js:31 +#~ msgid "Unable to find DKIM scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:34 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:48 +msgid "Unable to find DMARC guidance tag(s). Please try again." +msgstr "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer." + +#: src/email-scan/loaders/load-dmarc-by-key.js:20 +#: src/email-scan/loaders/load-dmarc-by-key.js:34 +#~ msgid "Unable to find DMARC scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:37 +#: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:51 +msgid "Unable to find DMARC summary data. Please try again." +msgstr "Impossible de trouver les données de synthèse DMARC. Veuillez réessayer." + +#: src/dns-scan/loaders/load-dns-by-key.js:20 +#: src/dns-scan/loaders/load-dns-by-key.js:34 +msgid "Unable to find DNS scan(s). Please try again." +msgstr "Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-guidance-tags.js:26 +#: src/guidance-tag/loaders/load-guidance-tags.js:36 +msgid "Unable to find guidance tag(s). Please try again." +msgstr "Impossible de trouver les étiquettes d'orientation. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-https-guidance-tags.js:33 +#: src/guidance-tag/loaders/load-https-guidance-tags.js:47 +msgid "Unable to find HTTPS guidance tag(s). Please try again." +msgstr "Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer." + +#: src/web-scan/loaders/load-https-by-key.js:19 +#~ msgid "Unable to find HTTPS scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-spf-guidance-tags.js:28 +#: src/guidance-tag/loaders/load-spf-guidance-tags.js:42 +msgid "Unable to find SPF guidance tag(s). Please try again." +msgstr "Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer." + +#: src/email-scan/loaders/load-spf-by-key.js:19 +#: src/email-scan/loaders/load-spf-by-key.js:31 +#~ msgid "Unable to find SPF scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:28 +#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:42 +msgid "Unable to find SSL guidance tag(s). Please try again." +msgstr "Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer." + +#: src/web-scan/loaders/load-ssl-by-key.js:18 +#: src/web-scan/loaders/load-ssl-by-key.js:30 +#~ msgid "Unable to find SSL scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer." + +#: src/domain/queries/find-domain-by-domain.js:41 +msgid "Unable to find the requested domain." +msgstr "Impossible de trouver le domaine demandé." + +#: src/affiliation/loaders/load-affiliation-by-key.js:22 +#: src/affiliation/loaders/load-affiliation-by-key.js:36 +msgid "Unable to find user affiliation(s). Please try again." +msgstr "Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer." + +#: src/verified-organizations/loaders/load-verified-organization-by-key.js:32 +#: src/verified-organizations/loaders/load-verified-organization-by-key.js:44 +#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:32 +#: src/verified-organizations/loaders/load-verified-organization-by-slug.js:44 +msgid "Unable to find verified organization(s). Please try again." +msgstr "Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer." + +#: src/domain/mutations/ignore-cve.js:64 +#: src/domain/mutations/ignore-cve.js:101 +#: src/domain/mutations/ignore-cve.js:123 +#: src/domain/mutations/ignore-cve.js:138 +#: src/domain/mutations/ignore-cve.js:149 +msgid "Unable to ignore CVE. Please try again." +msgstr "Impossible d'ignorer le CVE. Veuillez réessayer." + +#: src/affiliation/mutations/invite-user-to-org.js:124 +#: src/affiliation/mutations/invite-user-to-org.js:190 +msgid "Unable to invite user to organization. Please try again." +msgstr "Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer." + +#: src/affiliation/mutations/invite-user-to-org.js:202 +msgid "Unable to invite user to organization. User is already affiliated with organization." +msgstr "Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation." + +#: src/affiliation/mutations/invite-user-to-org.js:84 +msgid "Unable to invite user to unknown organization." +msgstr "Impossible d'inviter un utilisateur à une organisation inconnue." + +#: src/affiliation/mutations/invite-user-to-org.js:232 +#: src/affiliation/mutations/invite-user-to-org.js:253 +msgid "Unable to invite user. Please try again." +msgstr "Impossible d'inviter un utilisateur. Veuillez réessayer." + +#: src/affiliation/mutations/invite-user-to-org.js:70 +msgid "Unable to invite yourself to an org." +msgstr "Impossible de s'inviter à un org." + +#: src/affiliation/mutations/leave-organization.js:190 +#: src/affiliation/mutations/leave-organization.js:208 +#~ msgid "Unable to leave organization. Please try again." +#~ msgstr "Impossible de quitter l'organisation. Veuillez réessayer." + +#: src/affiliation/mutations/leave-organization.js:48 +msgid "Unable to leave undefined organization." +msgstr "Impossible de quitter une organisation non définie." + +#: src/additional-findings/loaders/load-additional-findings-by-domain-id.js:24 +#: src/additional-findings/loaders/load-additional-findings-by-domain-id.js:34 +msgid "Unable to load additional findings. Please try again." +msgstr "Impossible de charger des résultats supplémentaires. Veuillez réessayer." + +#: src/auth/checks/check-user-belongs-to-org.js:20 +msgid "Unable to load affiliation information. Please try again." +msgstr "Impossible de charger les informations d'affiliation. Veuillez réessayer." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:303 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:449 +msgid "Unable to load affiliation(s). Please try again." +msgstr "Impossible de charger l'affiliation (s). Veuillez réessayer." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:254 +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:266 +msgid "Unable to load Aggregate guidance tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer." + +#: src/organization/loaders/load-all-organization-domain-statuses.js:57 +#~ msgid "Unable to load all organization domain statuses. Please try again." +#~ msgstr "Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:50 +#: src/summaries/loaders/load-chart-summaries-by-period.js:60 +msgid "Unable to load chart summary data. Please try again." +msgstr "Impossible de charger les données du résumé du graphique. Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:141 +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:153 +msgid "Unable to load DKIM failure data. Please try again." +msgstr "Impossible de charger les données d'échec DKIM. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:272 +msgid "Unable to load DKIM guidance tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:258 +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:270 +#~ msgid "Unable to load DKIM result(s). Please try again." +#~ msgstr "Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:279 +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:289 +#~ msgid "Unable to load DKIM scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer." + +#: src/summaries/queries/dkim-summary.js:12 +#~ msgid "Unable to load DKIM summary. Please try again." +#~ msgstr "Impossible de charger le résumé DKIM. Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:153 +msgid "Unable to load DMARC failure data. Please try again." +msgstr "Impossible de charger les données d'échec DMARC. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:272 +msgid "Unable to load DMARC guidance tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer." + +#: src/summaries/queries/dmarc-phase-summary.js:12 +#~ msgid "Unable to load DMARC phase summary. Please try again." +#~ msgstr "Impossible de charger le résumé DMARC. Veuillez réessayer." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:319 +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:331 +#~ msgid "Unable to load DMARC scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:449 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:459 +#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:20 +#: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:32 +#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:20 +#: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:32 +msgid "Unable to load DMARC summary data. Please try again." +msgstr "Impossible de charger les données de synthèse DMARC. Veuillez réessayer." + +#: src/summaries/queries/dmarc-summary.js:12 +#~ msgid "Unable to load DMARC summary. Please try again." +#~ msgstr "Impossible de charger le résumé DMARC. Veuillez réessayer." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:154 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:164 +msgid "Unable to load DNS scan(s). Please try again." +msgstr "Impossible de charger le(s) scan(s) DNS. Veuillez réessayer." + +#: src/domain/loaders/load-dkim-selectors-by-domain-id.js:18 +#: src/domain/loaders/load-dkim-selectors-by-domain-id.js:28 +msgid "Unable to load domain selector(s). Please try again." +msgstr "Impossible de charger le(s) sélecteur(s) de domaine. Veuillez réessayer." + +#: src/domain/loaders/load-domain-by-domain.js:19 +#: src/domain/loaders/load-domain-by-domain.js:31 +#: src/domain/loaders/load-domain-by-key.js:19 +#: src/domain/loaders/load-domain-by-key.js:31 +msgid "Unable to load domain. Please try again." +msgstr "Impossible de charger le domaine. Veuillez réessayer." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:402 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:412 +#: src/domain/loaders/load-domain-connections-by-user-id.js:469 +#: src/user/loaders/load-my-tracker-by-user-id.js:33 +msgid "Unable to load domain(s). Please try again." +msgstr "Impossible de charger le(s) domaine(s). Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:140 +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:152 +msgid "Unable to load full pass data. Please try again." +msgstr "Impossible de charger les données complètes de la passe. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-guidance-tags-connections.js:54 +#: src/guidance-tag/loaders/load-guidance-tags-connections.js:64 +msgid "Unable to load guidance tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s) d'orientation. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:272 +msgid "Unable to load HTTPS guidance tag(s). Please try again." +msgstr "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer." + +#: src/web-scan/loaders/load-https-by-key.js:33 +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:333 +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:345 +#~ msgid "Unable to load HTTPS scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer." + +#: src/summaries/queries/https-summary.js:13 +#~ msgid "Unable to load HTTPS summary. Please try again." +#~ msgstr "Impossible de charger le résumé HTTPS. Veuillez réessayer." + +#: src/audit-logs/loaders/load-audit-log-by-key.js:19 +#: src/audit-logs/loaders/load-audit-log-by-key.js:31 +msgid "Unable to load log. Please try again." +msgstr "Impossible de charger le journal. Veuillez réessayer." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:318 +msgid "Unable to load log(s). Please try again." +msgstr "Impossible de charger le(s) journal(s). Veuillez réessayer." + +#: src/summaries/queries/mail-summary.js:12 +#~ msgid "Unable to load mail summary. Please try again." +#~ msgstr "Impossible de charger le résumé du courrier. Veuillez réessayer." + +#: src/additional-findings/loaders/load-top-25-reports.js:29 +#: src/organization/loaders/load-all-organization-domain-statuses.js:164 +#: src/organization/loaders/load-organization-domain-statuses.js:172 +msgid "Unable to load organization domain statuses. Please try again." +msgstr "Impossible de charger les statuts des domaines d'organisation. Veuillez réessayer." + +#: src/organization/loaders/load-organization-names-by-id.js:19 +#: src/organization/loaders/load-organization-names-by-id.js:29 +msgid "Unable to load organization names. Please try again." +msgstr "Impossible de charger les noms des organisations. Veuillez réessayer." + +#: src/organization/loaders/load-organization-summaries-by-period.js:56 +#: src/organization/loaders/load-organization-summaries-by-period.js:66 +msgid "Unable to load organization summary data. Please try again." +msgstr "Impossible de charger les données de synthèse de l'organisation. Veuillez réessayer." + +#: src/organization/data-source.js:117 +#: src/organization/data-source.js:124 +#: src/organization/data-source.js:144 +#: src/organization/data-source.js:152 +msgid "Unable to load organization. Please try again." +msgstr "Impossible de charger l'organisation. Veuillez réessayer." + +#: src/organization/loaders/load-organization-by-key.js:31 +#: src/organization/loaders/load-organization-by-key.js:41 +#: src/organization/loaders/load-organization-by-slug.js:34 +#: src/organization/loaders/load-organization-by-slug.js:45 +#: src/organization/loaders/load-organization-connections-by-domain-id.js:508 +#: src/organization/loaders/load-organization-connections-by-domain-id.js:518 +#: src/organization/loaders/load-organization-connections-by-user-id.js:544 +#: src/organization/loaders/load-organization-connections-by-user-id.js:554 +msgid "Unable to load organization(s). Please try again." +msgstr "Impossible de charger l'organisation (s). Veuillez réessayer." + +#: src/auth/checks/check-org-owner.js:19 +#: src/auth/checks/check-org-owner.js:27 +#: src/auth/loaders/load-org-owner-by-org-id.js:23 +#: src/auth/loaders/load-org-owner-by-org-id.js:33 +msgid "Unable to load owner information. Please try again." +msgstr "Impossible de charger les informations sur le propriétaire. Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:13 +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:140 +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:152 +msgid "Unable to load SPF failure data. Please try again." +msgstr "Impossible de charger les données d'échec SPF. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:272 +msgid "Unable to load SPF guidance tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:306 +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:316 +#~ msgid "Unable to load SPF scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) SPF. Veuillez réessayer." + +#: src/summaries/queries/spf-summary.js:12 +#~ msgid "Unable to load SPF summary. Please try again." +#~ msgstr "Impossible de charger le résumé SPF. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 +msgid "Unable to load SSL guidance tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:380 +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:390 +#~ msgid "Unable to load SSL scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) SSL. Veuillez réessayer." + +#: src/summaries/queries/ssl-summary.js:12 +#~ msgid "Unable to load SSL summary. Please try again." +#~ msgstr "Impossible de charger le résumé SSL. Veuillez réessayer." + +#: src/summaries/loaders/load-chart-summary-by-key.js:17 +#: src/summaries/loaders/load-chart-summary-by-key.js:25 +#~ msgid "Unable to load summary. Please try again." +#~ msgstr "Impossible de charger le résumé. Veuillez réessayer." + +#: src/tags/loaders/load-all-tags.js:36 +#: src/tags/loaders/load-all-tags.js:44 +#: src/tags/loaders/load-tag-by-tag-id.js:25 +#: src/tags/loaders/load-tag-by-tag-id.js:35 +#: src/tags/loaders/load-tags-by-org.js:36 +#: src/tags/loaders/load-tags-by-org.js:44 +msgid "Unable to load tag(s). Please try again." +msgstr "Impossible de charger le(s) tag(s). Veuillez réessayer." + +#: src/domain/loaders/load-domain-tags-by-org-id.js:37 +#~ msgid "Unable to load tags(s). Please try again." +#~ msgstr "Impossible de charger les balises. Veuillez réessayer." + +#: src/user/loaders/load-user-by-key.js:19 +#: src/user/loaders/load-user-by-key.js:31 +#: src/user/loaders/load-user-by-username.js:19 +#: src/user/loaders/load-user-by-username.js:31 +#: src/user/loaders/load-user-connections-by-user-id.js:346 +msgid "Unable to load user(s). Please try again." +msgstr "Impossible de charger le(s) utilisateur(s). Veuillez réessayer." + +#: src/verified-domains/loaders/load-verified-domain-by-domain.js:24 +#: src/verified-domains/loaders/load-verified-domain-by-domain.js:38 +#: src/verified-domains/loaders/load-verified-domain-by-key.js:24 +#: src/verified-domains/loaders/load-verified-domain-by-key.js:38 +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:306 +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:318 +#: src/verified-domains/loaders/load-verified-domain-connections.js:312 +#: src/verified-domains/loaders/load-verified-domain-connections.js:324 +msgid "Unable to load verified domain(s). Please try again." +msgstr "Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:423 +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:435 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:422 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:434 +msgid "Unable to load verified organization(s). Please try again." +msgstr "Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer." + +#: src/dmarc-summaries/loaders/load-all-verified-rua-domains.js:27 +msgid "Unable to load verified rua domains. Please try again." +msgstr "Impossible de charger les domaines rua vérifiés. Veuillez réessayer." + +#: src/summaries/queries/web-connections-summary.js:12 +#~ msgid "Unable to load web connections summary. Please try again." +#~ msgstr "Impossible de charger le résumé des connexions web. Veuillez réessayer." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:169 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:179 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:9 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:25 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:35 +msgid "Unable to load web scan(s). Please try again." +msgstr "Impossible de charger le(s) scan(s) web. Veuillez réessayer." + +#: src/summaries/queries/web-summary.js:13 +#~ msgid "Unable to load web summary. Please try again." +#~ msgstr "Impossible de charger le résumé web. Veuillez réessayer." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:293 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:437 +msgid "Unable to query affiliation(s). Please try again." +msgstr "Impossible de demander l'affiliation (s). Veuillez réessayer." + +#: src/domain/loaders/load-domain-connections-by-user-id.js:459 +#: src/user/loaders/load-my-tracker-by-user-id.js:23 +msgid "Unable to query domain(s). Please try again." +msgstr "Impossible d'interroger le(s) domaine(s). Veuillez réessayer." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:308 +msgid "Unable to query log(s). Please try again." +msgstr "Impossible d'interroger le(s) journal(s). Veuillez réessayer." + +#: src/user/loaders/load-user-connections-by-user-id.js:336 +msgid "Unable to query user(s). Please try again." +msgstr "Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer." + +#: src/user/mutations/refresh-tokens.js:47 +#: src/user/mutations/refresh-tokens.js:59 +#: src/user/mutations/refresh-tokens.js:72 +#: src/user/mutations/refresh-tokens.js:85 +#: src/user/mutations/refresh-tokens.js:95 +#: src/user/mutations/refresh-tokens.js:123 +#: src/user/mutations/refresh-tokens.js:131 +msgid "Unable to refresh tokens, please sign in." +msgstr "Impossible de rafraîchir les jetons, veuillez vous connecter." + +#: src/affiliation/mutations/remove-user-from-org.js:106 +msgid "Unable to remove a user that already does not belong to this organization." +msgstr "Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation." + +#: src/domain/mutations/remove-domain.js:82 +msgid "Unable to remove domain from unknown organization." +msgstr "Impossible de supprimer le domaine d'une organisation inconnue." + +#: src/domain/mutations/remove-domain.js:138 +msgid "Unable to remove domain. Domain is not part of organization." +msgstr "Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation." + +#: src/domain/mutations/remove-domain.js:125 +#: src/domain/mutations/remove-domain.js:154 +#: src/domain/mutations/remove-domain.js:188 +#: src/domain/mutations/remove-domain.js:208 +#: src/domain/mutations/remove-domain.js:237 +#: src/domain/mutations/remove-domain.js:256 +#: src/domain/mutations/remove-domain.js:274 +#: src/domain/mutations/remove-domain.js:291 +#: src/domain/mutations/remove-domain.js:309 +#: src/domain/mutations/remove-domain.js:333 +#: src/domain/mutations/remove-domain.js:345 +msgid "Unable to remove domain. Please try again." +msgstr "Impossible de supprimer le domaine. Veuillez réessayer." + +#: src/domain/mutations/remove-organizations-domains.js:91 +msgid "Unable to remove domains from unknown organization." +msgstr "Impossible de supprimer les domaines d'une organisation inconnue." + +#: src/organization/data-source.js:278 +#: src/organization/data-source.js:307 +#: src/organization/data-source.js:321 +#: src/organization/data-source.js:350 +#: src/organization/data-source.js:374 +#: src/organization/data-source.js:390 +#: src/organization/data-source.js:405 +#: src/organization/data-source.js:419 +#: src/organization/data-source.js:448 +#: src/organization/data-source.js:490 +#: src/organization/data-source.js:498 +msgid "Unable to remove organization. Please try again." +msgstr "Impossible de supprimer l'organisation. Veuillez réessayer." + +#: src/user/mutations/remove-phone-number.js:51 +#: src/user/mutations/remove-phone-number.js:59 +msgid "Unable to remove phone number. Please try again." +msgstr "Impossible de supprimer le numéro de téléphone. Veuillez réessayer." + +#: src/domain/mutations/remove-domain.js:67 +msgid "Unable to remove unknown domain." +msgstr "Impossible de supprimer un domaine inconnu." + +#: src/organization/mutations/remove-organization.js:52 +msgid "Unable to remove unknown organization." +msgstr "Impossible de supprimer une organisation inconnue." + +#: src/affiliation/mutations/remove-user-from-org.js:79 +msgid "Unable to remove unknown user from organization." +msgstr "Impossible de supprimer un utilisateur inconnu de l'organisation." + +#: src/affiliation/mutations/remove-user-from-org.js:74 +#~ msgid "Unable to remove user from organization." +#~ msgstr "Impossible de supprimer un utilisateur de l'organisation." + +#: src/affiliation/mutations/remove-user-from-org.js:96 +#: src/affiliation/mutations/remove-user-from-org.js:117 +#: src/affiliation/mutations/remove-user-from-org.js:164 +#: src/affiliation/mutations/remove-user-from-org.js:174 +msgid "Unable to remove user from this organization. Please try again." +msgstr "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer." + +#: src/affiliation/mutations/remove-user-from-org.js:63 +msgid "Unable to remove user from unknown organization." +msgstr "Impossible de supprimer un utilisateur d'une organisation inconnue." + +#: src/domain/mutations/request-scan.js:120 +msgid "Unable to request a one time scan on a domain that already has a pending scan." +msgstr "Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours." + +#: src/domain/mutations/request-scan.js:55 +msgid "Unable to request a one time scan on an unknown domain." +msgstr "Impossible de demander un scan unique sur un domaine inconnu." + +#: src/domain/mutations/request-scan.js:128 +msgid "Unable to request a one time scan. Please try again." +msgstr "Impossible de demander une analyse unique. Veuillez réessayer." + +#: src/domain/mutations/request-discovery.js:63 +msgid "Unable to request a subdomain discovery scan on an invalid domain." +msgstr "Impossible de demander un scan de découverte de sous-domaine sur un domaine invalide." + +#: src/domain/mutations/request-discovery.js:73 +msgid "Unable to request a subdomain discovery scan on an unknown domain." +msgstr "Impossible de demander une analyse de découverte de sous-domaine sur un domaine inconnu." + +#: src/affiliation/mutations/request-org-affiliation.js:98 +msgid "Unable to request invite to organization with which you are already affiliated." +msgstr "Impossible de demander une invitation à une organisation à laquelle vous êtes déjà affilié." + +#: src/affiliation/mutations/request-org-affiliation.js:88 +msgid "Unable to request invite to organization with which you have already requested to join." +msgstr "Impossible de demander une invitation à une organisation à laquelle vous avez déjà demandé à adhérer." + +#: src/affiliation/mutations/request-org-affiliation.js:59 +msgid "Unable to request invite to unknown organization." +msgstr "Impossible de demander une invitation à une organisation inconnue." + +#: src/affiliation/mutations/request-org-affiliation.js:75 +#: src/affiliation/mutations/request-org-affiliation.js:124 +#: src/affiliation/mutations/request-org-affiliation.js:141 +#: src/affiliation/mutations/request-org-affiliation.js:152 +#: src/affiliation/mutations/request-org-affiliation.js:167 +#: src/affiliation/mutations/request-org-affiliation.js:198 +msgid "Unable to request invite. Please try again." +msgstr "Impossible de demander une invitation. Veuillez réessayer." + +#: src/user/mutations/reset-password.js:86 +#~ msgid "Unable to reset password. Please request a new email." +#~ msgstr "Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail." + +#: src/user/mutations/reset-password.js:76 +#: src/user/mutations/reset-password.js:125 +#: src/user/mutations/reset-password.js:133 +msgid "Unable to reset password. Please try again." +msgstr "Impossible de réinitialiser le mot de passe. Veuillez réessayer." + +#: src/domain/objects/domain.js:274 +#: src/domain/objects/domain.js:309 +msgid "Unable to retrieve DMARC report information for: {domain}" +msgstr "Impossible de récupérer les informations du rapport DMARC pour : {domain}" + +#: src/dmarc-summaries/loaders/load-start-date-from-period.js:33 +#: src/dmarc-summaries/loaders/load-start-date-from-period.js:48 +msgid "Unable to select DMARC report(s) for this period and year." +msgstr "Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année." + +#: src/notify/notify-send-authenticate-email.js:19 +msgid "Unable to send authentication email. Please try again." +msgstr "Impossible d'envoyer l'email d'authentification. Veuillez réessayer." + +#: src/notify/notify-send-authenticate-text-msg.js:32 +msgid "Unable to send authentication text message. Please try again." +msgstr "Impossible d'envoyer un message texte d'authentification. Veuillez réessayer." + +#: src/notify/notify-send-org-invite-create-account.js:19 +#: src/notify/notify-send-org-invite-email.js:18 +msgid "Unable to send org invite email. Please try again." +msgstr "Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer." + +#: src/notify/notify-send-invite-request-email.js:19 +msgid "Unable to send org invite request email. Please try again." +msgstr "Impossible d'envoyer l'email de demande d'invitation à l'org. Veuillez réessayer." + +#: src/notify/notify-send-password-reset-email.js:18 +msgid "Unable to send password reset email. Please try again." +msgstr "Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer." + +#: src/notify/notify-send-role-change-email.js:21 +#~ msgid "Unable to send role update email. Please try again." +#~ msgstr "Impossible d'envoyer l'e-mail de mise à jour du rôle. Veuillez réessayer." + +#: src/notify/notify-send-tfa-text-msg.js:30 +#~ msgid "Unable to send two factor authentication message. Please try again." +#~ msgstr "Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer." + +#: src/notify/notify-send-updated-username-email.js:18 +#: src/user/mutations/verify-account.js:102 +msgid "Unable to send updated username email. Please try again." +msgstr "Impossible d'envoyer l'email de mise à jour du nom d'utilisateur. Veuillez réessayer." + +#: src/notify/notify-send-verification-email.js:18 +msgid "Unable to send verification email. Please try again." +msgstr "Impossible d'envoyer l'email de vérification. Veuillez réessayer." + +#: src/user/mutations/set-phone-number.js:97 +#: src/user/mutations/set-phone-number.js:105 +msgid "Unable to set phone number, please try again." +msgstr "Impossible de définir le numéro de téléphone, veuillez réessayer." + +#: src/user/mutations/sign-in.js:98 +#: src/user/mutations/sign-in.js:132 +#: src/user/mutations/sign-in.js:140 +#: src/user/mutations/sign-in.js:198 +#: src/user/mutations/sign-in.js:206 +#: src/user/mutations/sign-in.js:270 +#: src/user/mutations/sign-in.js:278 +msgid "Unable to sign in, please try again." +msgstr "Impossible de se connecter, veuillez réessayer." + +#: src/user/mutations/sign-up.js:197 +#: src/user/mutations/sign-up.js:208 +msgid "Unable to sign up, please contact org admin for a new invite." +msgstr "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation." + +#: src/user/mutations/sign-up.js:168 +#: src/user/mutations/sign-up.js:177 +#: src/user/mutations/sign-up.js:229 +#: src/user/mutations/sign-up.js:238 +msgid "Unable to sign up. Please try again." +msgstr "Impossible de s'inscrire. Veuillez réessayer." + +#: src/domain/mutations/unignore-cve.js:64 +#: src/domain/mutations/unignore-cve.js:101 +#: src/domain/mutations/unignore-cve.js:123 +#: src/domain/mutations/unignore-cve.js:138 +#: src/domain/mutations/unignore-cve.js:149 +msgid "Unable to stop ignoring CVE. Please try again." +msgstr "Impossible d'arrêter d'ignorer le CVE. Veuillez réessayer." + +#: src/affiliation/mutations/transfer-org-ownership.js:106 +#: src/affiliation/mutations/transfer-org-ownership.js:146 +#: src/affiliation/mutations/transfer-org-ownership.js:169 +#: src/affiliation/mutations/transfer-org-ownership.js:180 +msgid "Unable to transfer organization ownership. Please try again." +msgstr "Impossible de transférer la propriété de l'organisation. Veuillez réessayer." + +#: src/affiliation/mutations/transfer-org-ownership.js:68 +#~ msgid "Unable to transfer ownership of a verified organization." +#~ msgstr "Impossible de transférer la propriété d'une organisation vérifiée." + +#: src/affiliation/mutations/transfer-org-ownership.js:89 +msgid "Unable to transfer ownership of an org to an undefined user." +msgstr "Impossible de transférer la propriété d'un org à un utilisateur non défini." + +#: src/affiliation/mutations/transfer-org-ownership.js:59 +msgid "Unable to transfer ownership of undefined organization." +msgstr "Impossible de transférer la propriété d'une organisation non définie." + +#: src/affiliation/mutations/transfer-org-ownership.js:118 +msgid "Unable to transfer ownership to a user outside the org. Please invite the user and try again." +msgstr "Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer." + +#: src/user/mutations/verify-phone-number.js:72 +#: src/user/mutations/verify-phone-number.js:80 +msgid "Unable to two factor authenticate. Please try again." +msgstr "Impossible de s'authentifier par deux facteurs. Veuillez réessayer." + +#: src/domain/mutations/unfavourite-domain.js:84 +msgid "Unable to unfavourite domain, domain is not favourited." +msgstr "Impossible de désactiver le domaine, le domaine n'est pas favorisé." + +#: src/domain/mutations/unfavourite-domain.js:111 +#: src/domain/mutations/unfavourite-domain.js:119 +msgid "Unable to unfavourite domain. Please try again." +msgstr "Impossible de défavoriser le domaine. Veuillez réessayer." + +#: src/domain/mutations/unfavourite-domain.js:53 +msgid "Unable to unfavourite unknown domain." +msgstr "Impossible de défavoriser un domaine inconnu." + +#: src/domain/mutations/update-domain.js:261 +msgid "Unable to update domain edge. Please try again." +msgstr "Impossible de mettre à jour le bord du domaine. Veuillez réessayer." + +#: src/domain/mutations/update-domain.js:137 +msgid "Unable to update domain in an unknown org." +msgstr "Impossible de mettre à jour le domaine dans un org inconnu." + +#: src/domain/mutations/update-domain.js:178 +msgid "Unable to update domain that does not belong to the given organization." +msgstr "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée." + +#: src/domain/mutations/update-domain.js:168 +#: src/domain/mutations/update-domain.js:218 +#: src/domain/mutations/update-domain.js:272 +msgid "Unable to update domain. Please try again." +msgstr "Impossible de mettre à jour le domaine. Veuillez réessayer." + +#: src/domain/mutations/update-domains-by-domain-ids.js:68 +#: src/domain/mutations/update-domains-by-filters.js:76 +msgid "Unable to update domains in unknown organization." +msgstr "Impossible de mettre à jour les domaines dans une organisation inconnue." + +#: src/domain/mutations/update-domains-by-filters.js:229 +#: src/domain/mutations/update-domains-by-filters.js:237 +#: src/domain/mutations/update-domains-by-filters.js:244 +msgid "Unable to update domains. Please try again." +msgstr "Impossible de mettre à jour les domaines. Veuillez réessayer." + +#: src/organization/data-source.js:101 +msgid "Unable to update organization. Please try again." +msgstr "Impossible de mettre à jour l'organisation. Veuillez réessayer." + +#: src/user/mutations/update-user-password.js:52 +msgid "Unable to update password, current password does not match. Please try again." +msgstr "Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer." + +#: src/user/mutations/update-user-password.js:62 +msgid "Unable to update password, new passwords do not match. Please try again." +msgstr "Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer." + +#: src/user/mutations/update-user-password.js:74 +msgid "Unable to update password, passwords do not match requirements. Please try again." +msgstr "Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer." + +#: src/user/mutations/update-user-password.js:95 +#: src/user/mutations/update-user-password.js:103 +msgid "Unable to update password. Please try again." +msgstr "Impossible de mettre à jour le mot de passe. Veuillez réessayer." + +#: src/user/mutations/update-user-profile.js:154 +#: src/user/mutations/update-user-profile.js:162 +msgid "Unable to update profile. Please try again." +msgstr "Impossible de mettre à jour le profil. Veuillez réessayer." + +#: src/affiliation/mutations/update-user-role.js:97 +msgid "Unable to update role: organization unknown." +msgstr "Impossible de mettre à jour le rôle : organisation inconnue." + +#: src/affiliation/mutations/update-user-role.js:140 +msgid "Unable to update role: user does not belong to organization." +msgstr "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation." + +#: src/affiliation/mutations/update-user-role.js:83 +msgid "Unable to update role: user unknown." +msgstr "Impossible de mettre à jour le rôle : utilisateur inconnu." + +#: src/tags/mutations/update-tag.js:113 +msgid "Unable to update tag in unknown organization." +msgstr "Impossible de mettre à jour la balise dans une organisation inconnue." + +#: src/tags/mutations/update-tag.js:103 +msgid "Unable to update tag, orgId is invalid." +msgstr "Impossible de mettre à jour la balise, l'orgId est invalide." + +#: src/tags/mutations/update-tag.js:127 +#~ msgid "Unable to update tag, tagId already in use." +#~ msgstr "Impossible de mettre à jour la balise, l'identifiant de balise est déjà utilisé." + +#: src/tags/data-source.js:30 +#: src/tags/data-source.js:36 +#: src/tags/data-source.js:92 +#: src/tags/data-source.js:101 +msgid "Unable to update tag. Please try again." +msgstr "Impossible de mettre à jour la balise. Veuillez réessayer." + +#: src/domain/mutations/update-domain.js:123 +msgid "Unable to update unknown domain." +msgstr "Impossible de mettre à jour un domaine inconnu." + +#: src/organization/mutations/update-organization.js:137 +msgid "Unable to update unknown organization." +msgstr "Impossible de mettre à jour une organisation inconnue." + +#: src/tags/mutations/update-tag.js:87 +msgid "Unable to update unknown tag." +msgstr "Impossible de mettre à jour une étiquette inconnue." + +#: src/affiliation/mutations/update-user-role.js:130 +#: src/affiliation/mutations/update-user-role.js:152 +#: src/affiliation/mutations/update-user-role.js:196 +#: src/affiliation/mutations/update-user-role.js:206 +#: src/affiliation/mutations/update-user-role.js:217 +msgid "Unable to update user's role. Please try again." +msgstr "Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer." + +#: src/affiliation/mutations/update-user-role.js:69 +msgid "Unable to update your own role." +msgstr "Impossible de mettre à jour votre propre rôle." + +#: src/user/mutations/verify-account.js:51 +#: src/user/mutations/verify-account.js:63 +#: src/user/mutations/verify-account.js:77 +msgid "Unable to verify account. Please request a new email." +msgstr "Impossible de vérifier le compte. Veuillez demander un nouvel e-mail." + +#: src/user/mutations/verify-account.js:128 +#: src/user/mutations/verify-account.js:136 +msgid "Unable to verify account. Please try again." +msgstr "Impossible de vérifier le compte. Veuillez réessayer." + +#: src/user/queries/is-user-super-admin.js:24 +msgid "Unable to verify if user is a super admin, please try again." +msgstr "Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer." + +#: src/user/mutations/update-user-profile.js:99 +#: src/user/queries/is-user-admin.js:49 +msgid "Unable to verify if user is an admin, please try again." +msgstr "Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer." + +#: src/organization/data-source.js:237 +#: src/organization/data-source.js:252 +#: src/organization/data-source.js:260 +msgid "Unable to verify organization. Please try again." +msgstr "Impossible de vérifier l'organisation. Veuillez réessayer." + +#: src/organization/mutations/verify-organization.js:50 +msgid "Unable to verify unknown organization." +msgstr "Impossible de vérifier une organisation inconnue." + +#: src/user/queries/find-user-by-username.js:41 +msgid "User could not be queried." +msgstr "L'utilisateur n'a pas pu être interrogé." + +#: src/user/mutations/sign-up.js:79 +msgid "User is trying to register for a non-production environment." +msgstr "L'utilisateur essaie de s'enregistrer dans un environnement de non-production." + +#: src/affiliation/mutations/update-user-role.js:253 +msgid "User role was updated successfully." +msgstr "Le rôle de l'utilisateur a été mis à jour avec succès." + +#: src/user/mutations/update-user-profile.js:80 +#: src/user/mutations/verify-account.js:88 +msgid "Username not available, please try another." +msgstr "Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre." + +#: src/auth/guards/tfa-required.js:15 +msgid "Verification error. Please activate multi-factor authentication to access content." +msgstr "Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu." + +#: src/auth/guards/verified-required.js:15 +msgid "Verification error. Please verify your account via email to access content." +msgstr "Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu." + +#: src/additional-findings/loaders/load-additional-findings-by-domain-id.js:8 +msgid "You must provide a `domainId` to retrieve a domain's additional findings." +msgstr "Vous devez fournir un `domainId` pour récupérer les résultats supplémentaires d'un domaine." + +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:82 +#: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:161 +msgid "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`." + +#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:114 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`." + +#: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`." + +#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:89 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`." + +#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:138 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`." + +#: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:162 +msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`." + +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:141 +#: src/domain/loaders/load-domain-connections-by-user-id.js:166 +msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`." + +#: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`." + +#: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:85 +#: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:89 +#: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:89 +msgid "You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`." + +#: src/web-scan/loaders/load-https-connections-by-domain-id.js:147 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`." + +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:100 +msgid "You must provide a `first` or `last` value to properly paginate the `Log` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Log`." + +#: src/organization/loaders/load-organization-connections-by-domain-id.js:166 +#: src/organization/loaders/load-organization-connections-by-user-id.js:178 +msgid "You must provide a `first` or `last` value to properly paginate the `Organization` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`." + +#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:133 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `SPF` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`." + +#: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:36 +msgid "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`." + +#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:171 +#~ msgid "You must provide a `first` or `last` value to properly paginate the `SSL` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`." + +#: src/user/loaders/load-user-connections-by-user-id.js:106 +msgid "You must provide a `first` or `last` value to properly paginate the `User` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `User`." + +#: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:117 +#: src/verified-domains/loaders/load-verified-domain-connections.js:117 +msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`." + +#: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:167 +#: src/verified-organizations/loaders/load-verified-organizations-connections.js:165 +msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." +msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:16 +msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection." +msgstr "Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `DNS`." + +#: src/dns-scan/loaders/load-mx-record-diff-by-domain-id.js:16 +#~ msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `MXRecord` connection." +#~ msgstr "Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `MXRecord`." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:16 +msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection." +msgstr "Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `web`." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:9 +msgid "You must provide a `limit` value to properly paginate the `DNS` connection." +msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`." + +#: src/dns-scan/loaders/load-mx-record-diff-by-domain-id.js:9 +#~ msgid "You must provide a `limit` value to properly paginate the `MXRecord` connection." +#~ msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la connexion `MXRecord`." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:9 +msgid "You must provide a `limit` value to properly paginate the `web` connection." +msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:8 +#~ msgid "You must provide a `period` value to access the `ChartSummaries` connection." +#~ msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `ChartSummaries`." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:12 +msgid "You must provide a `period` value to access the `DmarcSummaries` connection." +msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`." + +#: src/organization/loaders/load-organization-summaries-by-period.js:10 +#~ msgid "You must provide a `period` value to access the `OrganizationSummaries` connection." +#~ msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `OrganizationSummaries`." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:43 +#~ msgid "You must provide a `year` value to access the `ChartSummaries` connection." +#~ msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `ChartSummaries`." + +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:18 +msgid "You must provide a `year` value to access the `DmarcSummaries` connection." +msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`." + +#: src/organization/loaders/load-organization-summaries-by-period.js:46 +#~ msgid "You must provide a `year` value to access the `OrganizationSummaries` connection." +#~ msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `OrganizationSummaries`." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:30 +msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection." +msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`." + +#: src/dns-scan/loaders/load-mx-record-diff-by-domain-id.js:30 +#~ msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `MXRecord` connection." +#~ msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `MXRecord`." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:30 +msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." +msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`." + +#: src/summaries/loaders/load-chart-summaries-by-period.js:13 +#~ msgid "You must provide both `startDate` and `endDate` values to access the `ChartSummaries` connection." +#~ msgstr "Vous devez fournir les valeurs « startDate » et « endDate » pour accéder à la connexion « ChartSummaries »." + +#: src/organization/loaders/load-organization-summaries-by-period.js:13 +#~ msgid "You must provide both `startDate` and `endDate` values to access the `OrganizationSummaries` connection." +#~ msgstr "Vous devez fournir les valeurs « startDate » et « endDate » pour accéder à la connexion « OrganizationSummaries »." diff --git a/api-js/src/mutation.js b/api/src/mutation.js similarity index 89% rename from api-js/src/mutation.js rename to api/src/mutation.js index 632ec995a0..2bdcb1ddff 100644 --- a/api-js/src/mutation.js +++ b/api/src/mutation.js @@ -4,6 +4,7 @@ import * as affiliationMutations from './affiliation/mutations' import * as domainMutations from './domain/mutations' import * as organizationMutations from './organization/mutations' import * as userMutations from './user/mutations' +import * as tagMutations from './tags/mutations' export const createMutationSchema = () => { return new GraphQLObjectType({ @@ -17,6 +18,7 @@ export const createMutationSchema = () => { ...organizationMutations, // User Mutations ...userMutations, + ...tagMutations, }), }) } diff --git a/api/src/node.js b/api/src/node.js new file mode 100644 index 0000000000..f5cac5713d --- /dev/null +++ b/api/src/node.js @@ -0,0 +1,12 @@ +import {nodeDefinitions} from 'graphql-relay' + +export const {nodeField, nodesField, nodeInterface} = nodeDefinitions( + (_globalId) => { + }, + (object) => { + switch (object) { + default: + return null + } + }, +) diff --git a/api-js/src/notify/__tests__/notify-send-authenticate-email.test.js b/api/src/notify/__tests__/notify-send-authenticate-email.test.js similarity index 83% rename from api-js/src/notify/__tests__/notify-send-authenticate-email.test.js rename to api/src/notify/__tests__/notify-send-authenticate-email.test.js index 5a88942a24..0fc651258f 100644 --- a/api-js/src/notify/__tests__/notify-send-authenticate-email.test.js +++ b/api/src/notify/__tests__/notify-send-authenticate-email.test.js @@ -1,10 +1,10 @@ -import { setupI18n } from '@lingui/core' +import {setupI18n} from '@lingui/core' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' -import { sendAuthEmail } from '../index' +import {sendAuthEmail} from '../index' -const { NOTIFICATION_AUTHENTICATE_EMAIL_ID } = process.env +const {NOTIFICATION_AUTHENTICATE_EMAIL_ID} = process.env describe('given the sendAuthEmail function', () => { let i18n @@ -16,8 +16,8 @@ describe('given the sendAuthEmail function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + en: {plurals: {}}, + fr: {plurals: {}}, }, locales: ['en', 'fr'], messages: { @@ -43,8 +43,8 @@ describe('given the sendAuthEmail function', () => { tfaCode: 123456, } - const mockedSendAuthEmail = sendAuthEmail({ notifyClient, i18n }) - await mockedSendAuthEmail({ user }) + const mockedSendAuthEmail = sendAuthEmail({notifyClient, i18n}) + await mockedSendAuthEmail({user}) expect(notifyClient.sendEmail).toHaveBeenCalledWith( NOTIFICATION_AUTHENTICATE_EMAIL_ID, @@ -63,8 +63,8 @@ describe('given the sendAuthEmail function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + en: {plurals: {}}, + fr: {plurals: {}}, }, locales: ['en', 'fr'], messages: { @@ -88,8 +88,8 @@ describe('given the sendAuthEmail function', () => { } try { - const mockedSendAuthEmail = sendAuthEmail({ notifyClient, i18n }) - await mockedSendAuthEmail({ user }) + const mockedSendAuthEmail = sendAuthEmail({notifyClient, i18n}) + await mockedSendAuthEmail({user}) } catch (err) { expect(err).toEqual( new Error('Unable to send authentication email. Please try again.'), @@ -107,8 +107,8 @@ describe('given the sendAuthEmail function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + en: {plurals: {}}, + fr: {plurals: {}}, }, locales: ['en', 'fr'], messages: { @@ -132,8 +132,8 @@ describe('given the sendAuthEmail function', () => { } try { - const mockedSendAuthEmail = sendAuthEmail({ notifyClient, i18n }) - await mockedSendAuthEmail({ user }) + const mockedSendAuthEmail = sendAuthEmail({notifyClient, i18n}) + await mockedSendAuthEmail({user}) } catch (err) { expect(err).toEqual( new Error( diff --git a/api-js/src/notify/__tests__/notify-send-authenticate-text-msg.test.js b/api/src/notify/__tests__/notify-send-authenticate-text-msg.test.js similarity index 85% rename from api-js/src/notify/__tests__/notify-send-authenticate-text-msg.test.js rename to api/src/notify/__tests__/notify-send-authenticate-text-msg.test.js index d306674978..ef6c113864 100644 --- a/api-js/src/notify/__tests__/notify-send-authenticate-text-msg.test.js +++ b/api/src/notify/__tests__/notify-send-authenticate-text-msg.test.js @@ -1,11 +1,11 @@ import crypto from 'crypto' -import { setupI18n } from '@lingui/core' +import {setupI18n} from '@lingui/core' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' -import { sendAuthTextMsg } from '../index' +import {sendAuthTextMsg} from '../index' -const { CIPHER_KEY, NOTIFICATION_AUTHENTICATE_TEXT_ID } = process.env +const {CIPHER_KEY, NOTIFICATION_AUTHENTICATE_TEXT_ID} = process.env describe('given the sendAuthTextMsg function', () => { let i18n @@ -17,8 +17,8 @@ describe('given the sendAuthTextMsg function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + en: {plurals: {}}, + fr: {plurals: {}}, }, locales: ['en', 'fr'], messages: { @@ -45,7 +45,7 @@ describe('given the sendAuthTextMsg function', () => { 'aes-256-ccm', String(CIPHER_KEY), Buffer.from(phoneDetails.iv, 'hex'), - { authTagLength: 16 }, + {authTagLength: 16}, ) let encrypted = cipher.update('+12345678901', 'utf8', 'hex') encrypted += cipher.final('hex') @@ -59,8 +59,8 @@ describe('given the sendAuthTextMsg function', () => { phoneDetails, } - const mockedSendAuthTextMsg = sendAuthTextMsg({ notifyClient, i18n }) - await mockedSendAuthTextMsg({ user }) + const mockedSendAuthTextMsg = sendAuthTextMsg({notifyClient, i18n}) + await mockedSendAuthTextMsg({user}) expect(notifyClient.sendSms).toHaveBeenCalledWith( NOTIFICATION_AUTHENTICATE_TEXT_ID, @@ -78,8 +78,8 @@ describe('given the sendAuthTextMsg function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + en: {plurals: {}}, + fr: {plurals: {}}, }, locales: ['en', 'fr'], messages: { @@ -103,7 +103,7 @@ describe('given the sendAuthTextMsg function', () => { 'aes-256-ccm', String(CIPHER_KEY), Buffer.from(phoneDetails.iv, 'hex'), - { authTagLength: 16 }, + {authTagLength: 16}, ) let encrypted = cipher.update('+12345678901', 'utf8', 'hex') encrypted += cipher.final('hex') @@ -118,8 +118,8 @@ describe('given the sendAuthTextMsg function', () => { } try { - const mockedSendAuthTextMsg = sendAuthTextMsg({ notifyClient, i18n }) - await mockedSendAuthTextMsg({ user }) + const mockedSendAuthTextMsg = sendAuthTextMsg({notifyClient, i18n}) + await mockedSendAuthTextMsg({user}) } catch (err) { expect(err).toEqual( new Error( @@ -139,8 +139,8 @@ describe('given the sendAuthTextMsg function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + en: {plurals: {}}, + fr: {plurals: {}}, }, locales: ['en', 'fr'], messages: { @@ -164,7 +164,7 @@ describe('given the sendAuthTextMsg function', () => { 'aes-256-ccm', String(CIPHER_KEY), Buffer.from(phoneDetails.iv, 'hex'), - { authTagLength: 16 }, + {authTagLength: 16}, ) let encrypted = cipher.update('+12345678901', 'utf8', 'hex') encrypted += cipher.final('hex') @@ -179,8 +179,8 @@ describe('given the sendAuthTextMsg function', () => { } try { - const mockedSendAuthTextMsg = sendAuthTextMsg({ notifyClient, i18n }) - await mockedSendAuthTextMsg({ user }) + const mockedSendAuthTextMsg = sendAuthTextMsg({notifyClient, i18n}) + await mockedSendAuthTextMsg({user}) } catch (err) { expect(err).toEqual( new Error( diff --git a/api-js/src/notify/__tests__/notify-send-org-invite-create-account.test.js b/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js similarity index 82% rename from api-js/src/notify/__tests__/notify-send-org-invite-create-account.test.js rename to api/src/notify/__tests__/notify-send-org-invite-create-account.test.js index 7e2beb447b..feccc6e333 100644 --- a/api-js/src/notify/__tests__/notify-send-org-invite-create-account.test.js +++ b/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js @@ -4,10 +4,7 @@ import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import { sendOrgInviteCreateAccount } from '../index' -const { - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, -} = process.env +const { NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL } = process.env describe('given the sendOrgInviteCreateAccount function', () => { let i18n @@ -58,7 +55,6 @@ describe('given the sendOrgInviteCreateAccount function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'english', } const mockedSendOrgInviteCreateAccount = sendOrgInviteCreateAccount({ @@ -67,18 +63,20 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) await mockedSendOrgInviteCreateAccount({ user, - orgName: 'Test Org', + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', createAccountLink: 'TestLink.ca', }) expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, + NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL, user.userName, { personalisation: { create_account_link: 'TestLink.ca', display_name: user.userName, - organization_name: 'Test Org', + organization_name_en: 'Test Org EN', + organization_name_fr: 'Test Org FR', }, }, ) @@ -86,9 +84,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) describe('an error occurs while sending email', () => { it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -96,7 +92,6 @@ describe('given the sendOrgInviteCreateAccount function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'english', } try { @@ -110,9 +105,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { createAccountLink: 'TestLink.ca', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to send org invite email. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to send org invite email. Please try again.')) } expect(consoleOutput).toEqual([ @@ -146,7 +139,6 @@ describe('given the sendOrgInviteCreateAccount function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'french', } const mockedSendOrgInviteCreateAccount = sendOrgInviteCreateAccount({ @@ -155,18 +147,20 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) await mockedSendOrgInviteCreateAccount({ user, - orgName: 'Test Org', + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', createAccountLink: 'TestLink.ca', }) expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, + NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL, user.userName, { personalisation: { create_account_link: 'TestLink.ca', display_name: user.userName, - organization_name: 'Test Org', + organization_name_en: 'Test Org EN', + organization_name_fr: 'Test Org FR', }, }, ) @@ -174,9 +168,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) describe('an error occurs while sending email', () => { it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -184,7 +176,6 @@ describe('given the sendOrgInviteCreateAccount function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'french', } try { @@ -198,9 +189,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { createAccountLink: 'TestLink.ca', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to send org invite email. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to send org invite email. Please try again.')) } expect(consoleOutput).toEqual([ diff --git a/api/src/notify/__tests__/notify-send-org-invite-email.test.js b/api/src/notify/__tests__/notify-send-org-invite-email.test.js new file mode 100644 index 0000000000..59dabcc567 --- /dev/null +++ b/api/src/notify/__tests__/notify-send-org-invite-email.test.js @@ -0,0 +1,189 @@ +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../locale/en/messages' +import frenchMessages from '../../locale/fr/messages' +import { sendOrgInviteEmail } from '../index' + +const { NOTIFICATION_ORG_INVITE_BILINGUAL } = process.env + +describe('given the sendOrgInviteEmail function', () => { + let i18n + let consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(async () => { + console.error = mockedError + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + + beforeEach(async () => { + consoleOutput = [] + }) + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('email successfully sent', () => { + it('returns nothing', async () => { + const sendEmail = jest.fn() + const notifyClient = { + sendEmail, + } + + const user = { + userName: 'test@email.ca', + displayName: 'Test Account', + } + + const mockedSendOrgInviteEmail = sendOrgInviteEmail({ + notifyClient, + i18n, + }) + await mockedSendOrgInviteEmail({ + user, + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', + }) + + expect(notifyClient.sendEmail).toHaveBeenCalledWith(NOTIFICATION_ORG_INVITE_BILINGUAL, user.userName, { + personalisation: { + display_name: user.displayName, + organization_name_en: 'Test Org EN', + organization_name_fr: 'Test Org FR', + }, + }) + }) + }) + describe('an error occurs while sending email', () => { + it('throws an error message', async () => { + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) + const notifyClient = { + sendEmail, + } + + const user = { + userName: 'test@email.ca', + displayName: 'Test Account', + } + + try { + const mockedSendOrgInviteEmail = sendOrgInviteEmail({ + notifyClient, + i18n, + }) + await mockedSendOrgInviteEmail({ + user, + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', + }) + } catch (err) { + expect(err).toEqual(new Error('Unable to send org invite email. Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Error occurred when sending org invite email for ${user._key}: Error: Notification error occurred.`, + ]) + }) + }) + }) + describe('language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('email successfully sent', () => { + it('returns nothing', async () => { + const sendEmail = jest.fn() + const notifyClient = { + sendEmail, + } + + const user = { + userName: 'test@email.ca', + displayName: 'Test Account', + } + + const mockedSendOrgInviteEmail = sendOrgInviteEmail({ + notifyClient, + i18n, + }) + await mockedSendOrgInviteEmail({ + user, + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', + }) + + expect(notifyClient.sendEmail).toHaveBeenCalledWith(NOTIFICATION_ORG_INVITE_BILINGUAL, user.userName, { + personalisation: { + display_name: user.displayName, + organization_name_en: 'Test Org EN', + organization_name_fr: 'Test Org FR', + }, + }) + }) + }) + describe('an error occurs while sending email', () => { + it('throws an error message', async () => { + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) + const notifyClient = { + sendEmail, + } + + const user = { + userName: 'test@email.ca', + displayName: 'Test Account', + } + + try { + const mockedSendOrgInviteEmail = sendOrgInviteEmail({ + notifyClient, + i18n, + }) + await mockedSendOrgInviteEmail({ + user, + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', + }) + } catch (err) { + expect(err).toEqual(new Error("Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.")) + } + + expect(consoleOutput).toEqual([ + `Error occurred when sending org invite email for ${user._key}: Error: Notification error occurred.`, + ]) + }) + }) + }) +}) diff --git a/api-js/src/notify/__tests__/notify-send-password-reset-email.test.js b/api/src/notify/__tests__/notify-send-password-reset-email.test.js similarity index 76% rename from api-js/src/notify/__tests__/notify-send-password-reset-email.test.js rename to api/src/notify/__tests__/notify-send-password-reset-email.test.js index 4197c5cc08..b906d15d63 100644 --- a/api-js/src/notify/__tests__/notify-send-password-reset-email.test.js +++ b/api/src/notify/__tests__/notify-send-password-reset-email.test.js @@ -4,10 +4,7 @@ import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import { sendPasswordResetEmail } from '../index' -const { - NOTIFICATION_PASSWORD_RESET_EN, - NOTIFICATION_PASSWORD_RESET_FR, -} = process.env +const { NOTIFICATION_PASSWORD_RESET_BILINGUAL } = process.env describe('given the sendPasswordResetEmail function', () => { let i18n @@ -59,7 +56,6 @@ describe('given the sendPasswordResetEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'english', } const mockedSendPasswordResetEmail = sendPasswordResetEmail({ @@ -71,23 +67,17 @@ describe('given the sendPasswordResetEmail function', () => { resetUrl: 'reset.url', }) - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_PASSWORD_RESET_EN, - user.userName, - { - personalisation: { - user: user.displayName, - password_reset_url: 'reset.url', - }, + expect(notifyClient.sendEmail).toHaveBeenCalledWith(NOTIFICATION_PASSWORD_RESET_BILINGUAL, user.userName, { + personalisation: { + user: user.displayName, + password_reset_url: 'reset.url', }, - ) + }) }) }) describe('an error occurs while sending email', () => { it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -95,7 +85,6 @@ describe('given the sendPasswordResetEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'english', } try { @@ -108,9 +97,7 @@ describe('given the sendPasswordResetEmail function', () => { resetUrl: 'reset.url', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to send password reset email. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to send password reset email. Please try again.')) } expect(consoleOutput).toEqual([ @@ -144,7 +131,6 @@ describe('given the sendPasswordResetEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'french', } const mockedSendPasswordResetEmail = sendPasswordResetEmail({ @@ -156,23 +142,17 @@ describe('given the sendPasswordResetEmail function', () => { resetUrl: 'reset.url', }) - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_PASSWORD_RESET_FR, - user.userName, - { - personalisation: { - user: user.displayName, - password_reset_url: 'reset.url', - }, + expect(notifyClient.sendEmail).toHaveBeenCalledWith(NOTIFICATION_PASSWORD_RESET_BILINGUAL, user.userName, { + personalisation: { + user: user.displayName, + password_reset_url: 'reset.url', }, - ) + }) }) }) describe('an error occurs while sending email', () => { it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -180,7 +160,6 @@ describe('given the sendPasswordResetEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'french', } try { @@ -194,9 +173,7 @@ describe('given the sendPasswordResetEmail function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - "Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.", - ), + new Error("Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer."), ) } diff --git a/api/src/notify/index.js b/api/src/notify/index.js new file mode 100644 index 0000000000..9e306a9a0a --- /dev/null +++ b/api/src/notify/index.js @@ -0,0 +1,10 @@ +export * from './notify-client' +export * from './notify-send-authenticate-email' +export * from './notify-send-authenticate-text-msg' +export * from './notify-send-invite-request-email' +export * from './notify-send-org-invite-create-account' +export * from './notify-send-org-invite-email' +export * from './notify-send-password-reset-email' +export * from './notify-send-updated-username-email' +export * from './notify-send-verification-email' +export * from './notify-send-role-change-email' diff --git a/api/src/notify/notify-client.js b/api/src/notify/notify-client.js new file mode 100644 index 0000000000..b20b762923 --- /dev/null +++ b/api/src/notify/notify-client.js @@ -0,0 +1,8 @@ +import {NotifyClient} from 'notifications-node-client' + +const {NOTIFICATION_API_KEY, NOTIFICATION_API_URL} = process.env + +export const notifyClient = new NotifyClient( + NOTIFICATION_API_URL, + NOTIFICATION_API_KEY, +) diff --git a/api/src/notify/notify-send-authenticate-email.js b/api/src/notify/notify-send-authenticate-email.js new file mode 100644 index 0000000000..08b9c26c94 --- /dev/null +++ b/api/src/notify/notify-send-authenticate-email.js @@ -0,0 +1,22 @@ +import {t} from '@lingui/macro' + +const {NOTIFICATION_AUTHENTICATE_EMAIL_ID} = process.env + +export const sendAuthEmail = ({notifyClient, i18n}) => async ({user}) => { + const templateId = NOTIFICATION_AUTHENTICATE_EMAIL_ID + try { + await notifyClient.sendEmail(templateId, user.userName, { + personalisation: { + user: user.displayName, + tfa_code: user.tfaCode, + }, + }) + } catch (err) { + console.error( + `Error occurred when sending authentication code via email for ${user._key}: ${err}`, + ) + throw new Error( + i18n._(t`Unable to send authentication email. Please try again.`), + ) + } +} diff --git a/api/src/notify/notify-send-authenticate-text-msg.js b/api/src/notify/notify-send-authenticate-text-msg.js new file mode 100644 index 0000000000..af9e3a93cf --- /dev/null +++ b/api/src/notify/notify-send-authenticate-text-msg.js @@ -0,0 +1,35 @@ +import crypto from 'crypto' +import {t} from '@lingui/macro' + +const {CIPHER_KEY, NOTIFICATION_AUTHENTICATE_TEXT_ID} = process.env + +export const sendAuthTextMsg = ({notifyClient, i18n}) => async ({user}) => { + const templateId = NOTIFICATION_AUTHENTICATE_TEXT_ID + + const {iv, tag, phoneNumber: encryptedData} = user.phoneDetails + const decipher = crypto.createDecipheriv( + 'aes-256-ccm', + String(CIPHER_KEY), + Buffer.from(iv, 'hex'), + {authTagLength: 16}, + ) + decipher.setAuthTag(Buffer.from(tag, 'hex')) + let phoneNumber = decipher.update(encryptedData, 'hex', 'utf8') + phoneNumber += decipher.final('utf8') + + try { + await notifyClient.sendSms(templateId, phoneNumber, { + personalisation: { + tfa_code: user.tfaCode, + }, + }) + return true + } catch (err) { + console.error( + `Error occurred when sending authentication code via text for ${user._key}: ${err}`, + ) + throw new Error( + i18n._(t`Unable to send authentication text message. Please try again.`), + ) + } +} diff --git a/api/src/notify/notify-send-invite-request-email.js b/api/src/notify/notify-send-invite-request-email.js new file mode 100644 index 0000000000..774f886722 --- /dev/null +++ b/api/src/notify/notify-send-invite-request-email.js @@ -0,0 +1,21 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL } = process.env + +export const sendInviteRequestEmail = + ({ notifyClient, i18n }) => + async ({ user, orgNameEN, orgNameFR, adminLink }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL, user.userName, { + personalisation: { + admin_link: adminLink, + display_name: user.displayName, + organization_name_en: orgNameEN, + organization_name_fr: orgNameFR, + }, + }) + } catch (err) { + console.error(`Error occurred when sending org invite request email for ${user._key}: ${err}`) + throw new Error(i18n._(t`Unable to send org invite request email. Please try again.`)) + } + } diff --git a/api/src/notify/notify-send-org-invite-create-account.js b/api/src/notify/notify-send-org-invite-create-account.js new file mode 100644 index 0000000000..9fb2e9aff3 --- /dev/null +++ b/api/src/notify/notify-send-org-invite-create-account.js @@ -0,0 +1,21 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL } = process.env + +export const sendOrgInviteCreateAccount = + ({ notifyClient, i18n }) => + async ({ user, orgNameEN, orgNameFR, createAccountLink }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL, user.userName, { + personalisation: { + create_account_link: createAccountLink, + display_name: user.userName, + organization_name_en: orgNameEN, + organization_name_fr: orgNameFR, + }, + }) + } catch (err) { + console.error(`Error occurred when sending org create account invite email for ${user._key}: ${err}`) + throw new Error(i18n._(t`Unable to send org invite email. Please try again.`)) + } + } diff --git a/api/src/notify/notify-send-org-invite-email.js b/api/src/notify/notify-send-org-invite-email.js new file mode 100644 index 0000000000..84e80b021e --- /dev/null +++ b/api/src/notify/notify-send-org-invite-email.js @@ -0,0 +1,20 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_ORG_INVITE_BILINGUAL } = process.env + +export const sendOrgInviteEmail = + ({ notifyClient, i18n }) => + async ({ user, orgNameEN, orgNameFR }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_ORG_INVITE_BILINGUAL, user.userName, { + personalisation: { + display_name: user.displayName, + organization_name_en: orgNameEN, + organization_name_fr: orgNameFR, + }, + }) + } catch (err) { + console.error(`Error occurred when sending org invite email for ${user._key}: ${err}`) + throw new Error(i18n._(t`Unable to send org invite email. Please try again.`)) + } + } diff --git a/api/src/notify/notify-send-password-reset-email.js b/api/src/notify/notify-send-password-reset-email.js new file mode 100644 index 0000000000..8901108dfb --- /dev/null +++ b/api/src/notify/notify-send-password-reset-email.js @@ -0,0 +1,20 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_PASSWORD_RESET_BILINGUAL } = process.env + +export const sendPasswordResetEmail = + ({ notifyClient, i18n }) => + async ({ user, resetUrl }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_PASSWORD_RESET_BILINGUAL, user.userName, { + personalisation: { + user: user.displayName, + password_reset_url: resetUrl, + }, + }) + return true + } catch (err) { + console.error(`Error occurred when sending password reset email for ${user._key}: ${err}`) + throw new Error(i18n._(t`Unable to send password reset email. Please try again.`)) + } + } diff --git a/api/src/notify/notify-send-role-change-email.js b/api/src/notify/notify-send-role-change-email.js new file mode 100644 index 0000000000..473339b698 --- /dev/null +++ b/api/src/notify/notify-send-role-change-email.js @@ -0,0 +1,20 @@ +const { NOTIFICATION_ROLE_CHANGE_EMAIL } = process.env + +export const sendRoleChangeEmail = + ({ notifyClient }) => + async ({ user, orgNames, oldRole, newRole }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_ROLE_CHANGE_EMAIL, user.userName, { + personalisation: { + display_name: user.displayName, + org_name_en: orgNames.orgNameEN, + org_name_fr: orgNames.orgNameFR, + old_role: oldRole, + new_role: newRole, + }, + }) + return true + } catch (err) { + console.error(`Error occurred when sending role update email for ${user._key}: ${err}`) + } + } diff --git a/api/src/notify/notify-send-updated-username-email.js b/api/src/notify/notify-send-updated-username-email.js new file mode 100644 index 0000000000..d5c819a00b --- /dev/null +++ b/api/src/notify/notify-send-updated-username-email.js @@ -0,0 +1,20 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_USERNAME_UPDATED_BILINGUAL } = process.env + +export const sendUpdatedUserNameEmail = + ({ notifyClient, i18n }) => + async ({ previousUserName, newUserName, displayName, userKey }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_USERNAME_UPDATED_BILINGUAL, previousUserName, { + personalisation: { + user: displayName, + new_username: newUserName, + }, + }) + return true + } catch (err) { + console.error(`Error occurred when sending updated username email for ${userKey}: ${err}`) + throw new Error(i18n._(t`Unable to send updated username email. Please try again.`)) + } + } diff --git a/api/src/notify/notify-send-verification-email.js b/api/src/notify/notify-send-verification-email.js new file mode 100644 index 0000000000..33ec8c43dc --- /dev/null +++ b/api/src/notify/notify-send-verification-email.js @@ -0,0 +1,20 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL } = process.env + +export const sendVerificationEmail = + ({ notifyClient, i18n }) => + async ({ userName, displayName, verifyUrl, userKey }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL, userName, { + personalisation: { + user: displayName, + verify_email_url: verifyUrl, + }, + }) + return true + } catch (err) { + console.error(`Error occurred when sending verification email for ${userKey}: ${err}`) + throw new Error(i18n._(t`Unable to send verification email. Please try again.`)) + } + } diff --git a/api/src/organization/data-source.js b/api/src/organization/data-source.js new file mode 100644 index 0000000000..166622e7fd --- /dev/null +++ b/api/src/organization/data-source.js @@ -0,0 +1,501 @@ +import { t } from '@lingui/macro' + +import { + loadAllOrganizationDomainStatuses, + loadOrgByKey, + loadOrgBySlug, + loadOrgConnectionsByDomainId, + loadOrgConnectionsByUserId, + loadOrganizationDomainStatuses, + loadOrganizationNamesById, + loadOrganizationSummariesByPeriod, +} from './loaders' + +export class OrganizationDataSource { + constructor({ query, userKey, i18n, language, cleanseInput, loginRequiredBool, transaction, collections }) { + this._query = query + this._userKey = userKey + this._i18n = i18n + this._transaction = transaction + this._collections = collections + this.byKey = loadOrgByKey({ query, language, userKey, i18n }) + this.bySlug = loadOrgBySlug({ query, language, userKey, i18n }) + this.connectionsByDomainId = loadOrgConnectionsByDomainId({ query, language, userKey, cleanseInput, i18n, auth: { loginRequiredBool } }) + this.connectionsByUserId = loadOrgConnectionsByUserId({ query, userKey, cleanseInput, language, i18n, auth: { loginRequiredBool } }) + this.summariesByPeriod = loadOrganizationSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + this.namesById = loadOrganizationNamesById({ query, userKey, i18n }) + this.domainStatuses = loadOrganizationDomainStatuses({ query, userKey, i18n }) + this.allDomainStatuses = loadAllOrganizationDomainStatuses({ query, userKey, i18n, language }) + } + + async create({ organizationDetails, userId, language }) { + const trx = await this._transaction(this._collections) + + let cursor + try { + cursor = await trx.step( + () => this._query` + WITH organizations + INSERT ${organizationDetails} INTO organizations + RETURN MERGE( + { + _id: NEW._id, + _key: NEW._key, + _rev: NEW._rev, + _type: "organization", + id: NEW._key, + verified: NEW.verified, + domainCount: 0, + summaries: NEW.summaries + }, + TRANSLATE(${language}, NEW.orgDetails) + ) + `, + ) + } catch (err) { + console.error(`Database error occurred when user: ${this._userKey} was creating new organization: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to create organization. Please try again.`)) + } + const organization = await cursor.next() + + try { + await trx.step( + () => this._query` + WITH affiliations, organizations, users + INSERT { + _from: ${organization._id}, + _to: ${userId}, + permission: "owner", + } INTO affiliations + `, + ) + } catch (err) { + console.error(`Database error occurred when inserting affiliation for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to create organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction error occurred when committing new organization for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to create organization. Please try again.`)) + } + + return organization + } + + async checkNameInUse({ nameEN, nameFR }) { + let cursor + try { + cursor = await this._query` + WITH organizations + FOR org IN organizations + FILTER (org.orgDetails.en.name == ${nameEN}) OR (org.orgDetails.fr.name == ${nameFR}) + RETURN org + ` + } catch (err) { + console.error(`Database error occurred during name check for user: ${this._userKey}: ${err}`) + throw new Error(this._i18n._(t`Unable to update organization. Please try again.`)) + } + return cursor + } + + async getRawByKey({ orgKey }) { + let cursor + try { + cursor = await this._query` + WITH organizations + FOR org IN organizations + FILTER org._key == ${orgKey} + RETURN org + ` + } catch (err) { + console.error(`Database error occurred while retrieving org: ${orgKey} for user: ${this._userKey}: ${err}`) + throw new Error(this._i18n._(t`Unable to load organization. Please try again.`)) + } + + try { + return await cursor.next() + } catch (err) { + console.error(`Cursor error occurred while retrieving org: ${orgKey} for user: ${this._userKey}: ${err}`) + throw new Error(this._i18n._(t`Unable to load organization. Please try again.`)) + } + } + + async update({ orgKey, updatedOrgDetails }) { + const trx = await this._transaction(this._collections) + + try { + await trx.step( + () => this._query` + WITH organizations + UPSERT { _key: ${orgKey} } + INSERT ${updatedOrgDetails} + UPDATE ${updatedOrgDetails} + IN organizations + `, + ) + } catch (err) { + console.error(`Transaction error occurred while upserting org: ${orgKey} for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to load organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction error occurred while committing org: ${orgKey} for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to load organization. Please try again.`)) + } + } + + async archive({ organization }) { + const trx = await this._transaction(this._collections) + + let domainInfo + try { + const countCursor = await this._query` + WITH claims, domains, organizations + LET domainIds = ( + FOR v, e IN 1..1 OUTBOUND ${organization._id} claims + RETURN e._to + ) + FOR domain IN domains + FILTER domain._id IN domainIds + LET count = LENGTH( + FOR v, e IN 1..1 INBOUND domain._id claims + RETURN 1 + ) + RETURN { _id: domain._id, _key: domain._key, count } + ` + domainInfo = await countCursor.all() + } catch (err) { + console.error(`Database error occurred for user: ${this._userKey} while gathering domain count for archive of org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to archive organization. Please try again.`)) + } + + for (const domain of domainInfo) { + if (domain.count === 1) { + try { + await trx.step( + () => this._query` + WITH domains + UPDATE { _key: ${domain._key}, archived: true } IN domains + `, + ) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while archiving domains for org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to archive organization. Please try again.`)) + } + } + } + + try { + await trx.step( + () => this._query` + WITH organizations + UPDATE { _key: ${organization._key}, verified: false } IN organizations + `, + ) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while unverifying org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to archive organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred for user: ${this._userKey} while archiving org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to archive organization. Please try again.`)) + } + } + + async verify({ currentOrg }) { + const trx = await this._transaction(this._collections) + + try { + await trx.step( + () => this._query` + WITH organizations + UPSERT { _key: ${currentOrg._key} } + INSERT ${currentOrg} + UPDATE ${currentOrg} + IN organizations + `, + ) + } catch (err) { + console.error(`Transaction error occurred while upserting verified org: ${currentOrg._key} for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to verify organization. Please try again.`)) + } + + try { + await trx.step( + () => this._query` + WITH domains, claims + FOR v, e IN 1..1 OUTBOUND ${currentOrg._id} claims + FILTER v.archived == true + UPDATE v WITH { archived: false } IN domains + `, + ) + } catch (err) { + console.error(`Transaction error occurred while unarchiving affiliated domains for org: ${currentOrg._key} for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to verify organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction error occurred while committing verified org: ${currentOrg._key} for user: ${this._userKey}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to verify organization. Please try again.`)) + } + } + + async remove({ organization }) { + const trx = await this._transaction(this._collections) + + let dmarcSummaryCheckList + try { + const dmarcSummaryCheckCursor = await this._query` + WITH domains, ownership, dmarcSummaries, organizations + FOR v, e IN 1..1 OUTBOUND ${organization._id} ownership + RETURN e + ` + dmarcSummaryCheckList = await dmarcSummaryCheckCursor.all() + } catch (err) { + console.error(`Database error occurred for user: ${this._userKey} while getting dmarc summaries for org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + for (const ownership of dmarcSummaryCheckList) { + try { + await trx.step( + () => this._query` + WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries + LET dmarcSummaryEdges = ( + FOR v, e IN 1..1 OUTBOUND ${ownership._to} domainsToDmarcSummaries + RETURN { edgeKey: e._key, dmarcSummaryId: e._to } + ) + LET removeDmarcSummaryEdges = ( + FOR dmarcSummaryEdge IN dmarcSummaryEdges + REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries + OPTIONS { waitForSync: true } + ) + LET removeDmarcSummary = ( + FOR dmarcSummaryEdge IN dmarcSummaryEdges + LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key + REMOVE key IN dmarcSummaries + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing dmarc summaries for org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + try { + await trx.step( + () => this._query` + WITH ownership, organizations, domains + REMOVE ${ownership._key} IN ownership + OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing ownerships for org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + } + + let domainInfo + try { + const countCursor = await this._query` + WITH claims, domains, organizations + LET domainIds = ( + FOR v, e IN 1..1 OUTBOUND ${organization._id} claims + RETURN e._to + ) + FOR domain IN domains + FILTER domain._id IN domainIds + LET count = LENGTH( + FOR v, e IN 1..1 INBOUND domain._id claims + RETURN 1 + ) + RETURN { + "_id": domain._id, + "_key": domain._key, + "domain": domain.domain, + "count": count + } + ` + domainInfo = await countCursor.all() + } catch (err) { + console.error(`Database error occurred for user: ${this._userKey} while gathering domain count for removal of org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + for (const domain of domainInfo) { + if (domain.count === 1) { + try { + await trx.step(async () => { + await this._query` + WITH web, webScan + FOR webV, domainsWebEdge IN 1..1 OUTBOUND ${domain._id} domainsWeb + LET removeWebScansQuery = ( + FOR webScanV, webToWebScansV In 1..1 OUTBOUND webV._id webToWebScans + REMOVE webScanV IN webScan + REMOVE webToWebScansV IN webToWebScans + OPTIONS { waitForSync: true } + ) + REMOVE webV IN web + REMOVE domainsWebEdge IN domainsWeb + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing web data for ${domain.domain} in org: ${organization.slug}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + try { + await trx.step(async () => { + await this._query` + WITH dns + FOR dnsV, domainsDNSEdge IN 1..1 OUTBOUND ${domain._id} domainsDNS + REMOVE dnsV IN dns + REMOVE domainsDNSEdge IN domainsDNS + OPTIONS { waitForSync: true } + ` + }) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing DNS data for ${domain.domain} in org: ${organization.slug}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + try { + await trx.step(async () => { + await this._query` + WITH favourites, domains + FOR fav IN favourites + FILTER fav._to == ${domain._id} + REMOVE fav IN favourites + ` + }) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing favourites for ${domain.domain} in org: ${organization.slug}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + try { + await trx.step(async () => { + await this._query` + FOR e IN domainsToSelectors + FILTER e._from == ${domain._id} + REMOVE e IN domainsToSelectors + ` + }) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing DKIM selectors for ${domain.domain} in org: ${organization.slug}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + try { + await trx.step( + () => this._query` + WITH claims, domains, organizations + LET domainEdges = ( + FOR v, e IN 1..1 OUTBOUND ${organization._id} claims + FILTER e._to == ${domain._id} + RETURN { edgeKey: e._key, domainId: e._to } + ) + LET removeDomainEdges = ( + FOR domainEdge in domainEdges + REMOVE domainEdge.edgeKey IN claims + OPTIONS { waitForSync: true } + ) + LET removeDomain = ( + FOR domainEdge in domainEdges + LET key = PARSE_IDENTIFIER(domainEdge.domainId).key + REMOVE key IN domains + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing domains for org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + } + } + + try { + await trx.step( + () => this._query` + WITH affiliations, organizations, users + LET userEdges = ( + FOR v, e IN 1..1 OUTBOUND ${organization._id} affiliations + RETURN { edgeKey: e._key, userKey: e._to } + ) + LET removeUserEdges = ( + FOR userEdge IN userEdges + REMOVE userEdge.edgeKey IN affiliations + OPTIONS { waitForSync: true } + ) + RETURN true + `, + ) + + await trx.step( + () => this._query` + WITH organizations, organizationSummaries + FOR summary in organizationSummaries + FILTER summary.organization == ${organization._id} + REMOVE summary._key IN organizationSummaries + OPTIONS { waitForSync: true } + `, + ) + + await trx.step( + () => this._query` + WITH organizations + REMOVE ${organization._key} IN organizations + OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error(`Transaction error occurred for user: ${this._userKey} while removing affiliations and org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred for user: ${this._userKey} while removing org: ${organization._key}: ${err}`) + await trx.abort() + throw new Error(this._i18n._(t`Unable to remove organization. Please try again.`)) + } + } +} diff --git a/api/src/organization/index.js b/api/src/organization/index.js new file mode 100644 index 0000000000..9366445c50 --- /dev/null +++ b/api/src/organization/index.js @@ -0,0 +1,7 @@ +export * from './data-source' +export * from './inputs' +export * from './loaders' +export * from './mutations' +export * from './objects' +export * from './queries' +export * from './unions' diff --git a/api/src/organization/inputs/__tests__/organization-order.test.js b/api/src/organization/inputs/__tests__/organization-order.test.js new file mode 100644 index 0000000000..257ef39b29 --- /dev/null +++ b/api/src/organization/inputs/__tests__/organization-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { organizationOrder } from '../organization-order' +import { OrderDirection, OrganizationOrderField } from '../../../enums' + +describe('given the organizationOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = organizationOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = organizationOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(OrganizationOrderField)) + }) + }) +}) diff --git a/api-js/src/organization/inputs/index.js b/api/src/organization/inputs/index.js similarity index 100% rename from api-js/src/organization/inputs/index.js rename to api/src/organization/inputs/index.js diff --git a/api-js/src/organization/inputs/organization-order.js b/api/src/organization/inputs/organization-order.js similarity index 81% rename from api-js/src/organization/inputs/organization-order.js rename to api/src/organization/inputs/organization-order.js index 1c310dfdf5..56a885a4a8 100644 --- a/api-js/src/organization/inputs/organization-order.js +++ b/api/src/organization/inputs/organization-order.js @@ -7,11 +7,11 @@ export const organizationOrder = new GraphQLInputObjectType({ description: 'Ordering options for organization connections', fields: () => ({ field: { - type: GraphQLNonNull(OrganizationOrderField), + type: new GraphQLNonNull(OrganizationOrderField), description: 'The field to order organizations by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/organization/loaders/__tests__/load-organization-by-key.test.js b/api/src/organization/loaders/__tests__/load-organization-by-key.test.js similarity index 86% rename from api-js/src/organization/loaders/__tests__/load-organization-by-key.test.js rename to api/src/organization/loaders/__tests__/load-organization-by-key.test.js index 70900c2dc1..2cba5076d1 100644 --- a/api-js/src/organization/loaders/__tests__/load-organization-by-key.test.js +++ b/api/src/organization/loaders/__tests__/load-organization-by-key.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadOrgByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,16 +24,21 @@ describe('given a loadOrgByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.organizations.save({ - verified: true, + verified: false, + externalId: 'test', summaries: { web: { pass: 50, @@ -69,7 +75,8 @@ describe('given a loadOrgByKey dataloader', () => { }, }) await collections.organizations.save({ - verified: true, + verified: false, + externalId: 'test', summaries: { web: { pass: 50, @@ -134,7 +141,7 @@ describe('given a loadOrgByKey dataloader', () => { FOR org IN organizations FILTER org.orgDetails.en.slug == "communications-security-establishment" LET domains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, externalId: org.externalId, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -151,7 +158,7 @@ describe('given a loadOrgByKey dataloader', () => { const expectedCursor = await query` FOR org IN organizations LET domains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, externalId: org.externalId, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -188,7 +195,7 @@ describe('given a loadOrgByKey dataloader', () => { FOR org IN organizations FILTER org.orgDetails.fr.slug == "centre-de-la-securite-des-telecommunications" LET domains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, externalId: org.externalId, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -205,7 +212,7 @@ describe('given a loadOrgByKey dataloader', () => { const expectedCursor = await query` FOR org IN organizations LET domains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, externalId: org.externalId, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -239,9 +246,7 @@ describe('given a loadOrgByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadOrgByKey({ query: mockedQuery, language: 'en', @@ -252,9 +257,7 @@ describe('given a loadOrgByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -280,9 +283,7 @@ describe('given a loadOrgByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -308,9 +309,7 @@ describe('given a loadOrgByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadOrgByKey({ query: mockedQuery, language: 'fr', @@ -321,11 +320,7 @@ describe('given a loadOrgByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -351,11 +346,7 @@ describe('given a loadOrgByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/organization/loaders/__tests__/load-organization-by-slug.test.js b/api/src/organization/loaders/__tests__/load-organization-by-slug.test.js similarity index 90% rename from api-js/src/organization/loaders/__tests__/load-organization-by-slug.test.js rename to api/src/organization/loaders/__tests__/load-organization-by-slug.test.js index 5797006eab..26eb5d7065 100644 --- a/api-js/src/organization/loaders/__tests__/load-organization-by-slug.test.js +++ b/api/src/organization/loaders/__tests__/load-organization-by-slug.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadOrgBySlug } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,16 +24,21 @@ describe('given a loadOrgBySlug dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.organizations.save({ - verified: true, + verified: false, + externalId: 'test', summaries: { web: { pass: 50, @@ -69,7 +75,8 @@ describe('given a loadOrgBySlug dataloader', () => { }, }) await collections.organizations.save({ - verified: true, + verified: false, + externalId: 'test', orgDetails: { en: { slug: 'treasury-board-secretariat', @@ -130,6 +137,7 @@ describe('given a loadOrgBySlug dataloader', () => { _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(domains), summaries: org.summaries, slugEN: org.orgDetails.en.slug, @@ -212,11 +220,12 @@ describe('given a loadOrgBySlug dataloader', () => { _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(domains), summaries: org.summaries, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug - }, + }, TRANSLATE("fr", org.orgDetails) ) ` @@ -247,7 +256,7 @@ describe('given a loadOrgBySlug dataloader', () => { summaries: org.summaries, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug - }, + }, TRANSLATE("fr", org.orgDetails) ) ` @@ -283,9 +292,7 @@ describe('given a loadOrgBySlug dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadOrgBySlug({ query: mockedQuery, language: 'en', @@ -296,9 +303,7 @@ describe('given a loadOrgBySlug dataloader', () => { try { await loader.load('slug') } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -324,9 +329,7 @@ describe('given a loadOrgBySlug dataloader', () => { try { await loader.load('slug') } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -352,9 +355,7 @@ describe('given a loadOrgBySlug dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadOrgBySlug({ query: mockedQuery, language: 'fr', @@ -365,11 +366,7 @@ describe('given a loadOrgBySlug dataloader', () => { try { await loader.load('slug') } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -395,11 +392,7 @@ describe('given a loadOrgBySlug dataloader', () => { try { await loader.load('slug') } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/organization/loaders/__tests__/load-organization-connections-by-domain-id.test.js b/api/src/organization/loaders/__tests__/load-organization-connections-by-domain-id.test.js similarity index 94% rename from api-js/src/organization/loaders/__tests__/load-organization-connections-by-domain-id.test.js rename to api/src/organization/loaders/__tests__/load-organization-connections-by-domain-id.test.js index ab09586b09..71a4df6221 100644 --- a/api-js/src/organization/loaders/__tests__/load-organization-connections-by-domain-id.test.js +++ b/api/src/organization/loaders/__tests__/load-organization-connections-by-domain-id.test.js @@ -1,28 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadOrgConnectionsByDomainId, loadOrgByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the load organizations connection function', () => { - let query, - drop, - truncate, - collections, - user, - org, - orgTwo, - domain, - domainTwo, - domainThree, - i18n + let query, drop, truncate, collections, user, org, orgTwo, domain, domainTwo, domainThree, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -38,23 +29,27 @@ describe('given the load organizations connection function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) org = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 50, @@ -92,6 +87,7 @@ describe('given the load organizations connection function', () => { }) orgTwo = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 52, @@ -193,13 +189,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -242,13 +236,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -291,13 +283,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -339,13 +329,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -396,6 +384,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) @@ -439,6 +428,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) @@ -482,13 +472,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) const connectionArgs = { domainId: domain._id, @@ -518,10 +506,7 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -535,6 +520,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 51, @@ -596,6 +582,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -643,6 +630,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -692,6 +680,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -739,6 +728,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -788,6 +778,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -835,6 +826,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -884,6 +876,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -931,6 +924,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -980,6 +974,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1027,6 +1022,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1076,6 +1072,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1123,6 +1120,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1172,6 +1170,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1219,6 +1218,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1268,6 +1268,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1315,6 +1316,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1364,6 +1366,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1411,6 +1414,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1460,6 +1464,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1507,6 +1512,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1556,6 +1562,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1603,6 +1610,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1652,6 +1660,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1699,6 +1708,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1748,6 +1758,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1795,6 +1806,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1844,6 +1856,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1891,6 +1904,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1940,6 +1954,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -1987,6 +2002,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -2036,6 +2052,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -2083,6 +2100,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -2129,6 +2147,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { @@ -2161,13 +2180,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2216,13 +2233,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) const connectionArgs = { first: 5, @@ -2259,6 +2274,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 52, @@ -2312,14 +2328,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -2355,10 +2368,7 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[2]._key), }, } @@ -2374,14 +2384,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -2411,10 +2418,7 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2449,13 +2453,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2498,13 +2500,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2547,13 +2547,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2595,13 +2593,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2652,6 +2648,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) @@ -2695,6 +2692,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) @@ -2738,13 +2736,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) const connectionArgs = { domainId: domain._id, @@ -2774,10 +2770,7 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2791,6 +2784,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 51, @@ -2852,6 +2846,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -2899,6 +2894,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -2948,6 +2944,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -2995,6 +2992,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3044,6 +3042,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3091,6 +3090,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3140,6 +3140,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3187,6 +3188,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3236,6 +3238,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3283,6 +3286,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3332,6 +3336,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3379,6 +3384,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3428,6 +3434,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3475,6 +3482,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3524,6 +3532,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3571,6 +3580,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3620,6 +3630,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3667,6 +3678,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3716,6 +3728,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3763,6 +3776,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3812,6 +3826,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3859,6 +3874,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3908,6 +3924,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -3955,6 +3972,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4004,6 +4022,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4051,6 +4070,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4100,6 +4120,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4147,6 +4168,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4196,6 +4218,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4243,6 +4266,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4292,6 +4316,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4339,6 +4364,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { first: 5, @@ -4385,6 +4411,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { @@ -4417,13 +4444,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -4472,13 +4497,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key]) const connectionArgs = { first: 5, @@ -4515,6 +4538,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 52, @@ -4568,14 +4592,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -4611,10 +4632,7 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[2]._key), }, } @@ -4630,14 +4648,11 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - org._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([org._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -4667,10 +4682,7 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -4707,6 +4719,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4736,6 +4749,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4769,6 +4783,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4780,11 +4795,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `Organization` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `Organization` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -4800,6 +4811,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4811,11 +4823,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `Organization` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `Organization` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -4833,6 +4841,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4864,6 +4873,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4891,15 +4901,14 @@ describe('given the load organizations connection function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { @@ -4911,11 +4920,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -4927,15 +4932,14 @@ describe('given the load organizations connection function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { @@ -4947,11 +4951,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -4966,9 +4966,7 @@ describe('given the load organizations connection function', () => { describe('given a database error', () => { describe('when gathering organizations', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadOrgConnectionsByDomainId({ query, @@ -4976,6 +4974,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -4987,9 +4986,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -5013,6 +5010,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5024,11 +5022,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -5063,6 +5057,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5092,6 +5087,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5125,6 +5121,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5137,9 +5134,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `Organization` ne peut être inférieure à zéro.', - ), + new Error('`first` sur la connexion `Organization` ne peut être inférieure à zéro.'), ) } @@ -5156,6 +5151,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5167,11 +5163,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `Organization` ne peut être inférieure à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `Organization` ne peut être inférieure à zéro.')) } expect(consoleOutput).toEqual([ @@ -5189,6 +5181,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5220,6 +5213,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5247,15 +5241,14 @@ describe('given the load organizations connection function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByDomainId({ query, language: 'fr', userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { @@ -5268,9 +5261,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -5283,15 +5274,14 @@ describe('given the load organizations connection function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByDomainId({ query, language: 'fr', userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) const connectionArgs = { @@ -5304,9 +5294,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -5322,9 +5310,7 @@ describe('given the load organizations connection function', () => { describe('given a database error', () => { describe('when gathering organizations', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadOrgConnectionsByDomainId({ query, @@ -5332,6 +5318,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5343,11 +5330,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -5371,6 +5354,7 @@ describe('given the load organizations connection function', () => { userKey: user._key, cleanseInput, i18n, + auth: { loginRequiredBool: true }, }) try { @@ -5382,11 +5366,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/organization/loaders/__tests__/load-organization-connections-by-user-id.test.js b/api/src/organization/loaders/__tests__/load-organization-connections-by-user-id.test.js similarity index 90% rename from api-js/src/organization/loaders/__tests__/load-organization-connections-by-user-id.test.js rename to api/src/organization/loaders/__tests__/load-organization-connections-by-user-id.test.js index d7af0a4685..15e4c96922 100644 --- a/api-js/src/organization/loaders/__tests__/load-organization-connections-by-user-id.test.js +++ b/api/src/organization/loaders/__tests__/load-organization-connections-by-user-id.test.js @@ -1,28 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadOrgConnectionsByUserId, loadOrgByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the load organization connections by user id function', () => { - let query, - drop, - truncate, - collections, - user, - orgOne, - orgTwo, - i18n, - domain, - domainTwo, - domainThree + let query, drop, truncate, collections, user, orgOne, orgTwo, i18n, domain, domainTwo, domainThree const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -39,23 +30,27 @@ describe('given the load organization connections by user id function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) orgOne = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 50, @@ -93,6 +88,7 @@ describe('given the load organization connections by user id function', () => { }) orgTwo = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 52, @@ -195,13 +191,11 @@ describe('given the load organization connections by user id function', () => { cleanseInput, language: 'en', i18n, + auth: { loginRequired: true }, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -225,10 +219,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[1]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -242,15 +233,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -274,10 +263,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[0]._key), }, } @@ -291,15 +277,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -322,10 +306,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[0]._key), }, } @@ -339,15 +320,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -370,10 +349,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[1]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -405,6 +381,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -447,6 +424,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -483,15 +461,13 @@ describe('given the load organization connections by user id function', () => { describe('search field is left empty', () => { it('returns unfiltered organizations', async () => { const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -523,14 +499,8 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -543,6 +513,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 51, @@ -602,6 +573,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -631,10 +603,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -651,6 +620,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -680,10 +650,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -702,6 +669,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -731,10 +699,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -751,6 +716,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -780,10 +746,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -802,6 +765,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -831,10 +795,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -851,6 +812,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -880,10 +842,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -902,6 +861,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -931,10 +891,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -951,6 +908,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -980,10 +938,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1002,6 +957,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1031,10 +987,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1051,6 +1004,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1080,10 +1034,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1102,6 +1053,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1131,10 +1083,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1151,6 +1100,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1180,10 +1130,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1202,6 +1149,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1231,10 +1179,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1251,6 +1196,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1280,10 +1226,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1302,6 +1245,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1331,10 +1275,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1351,6 +1292,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1380,10 +1322,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1402,6 +1341,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1431,10 +1371,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1451,6 +1388,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1480,10 +1418,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1502,6 +1437,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1531,10 +1467,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1551,6 +1484,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1580,10 +1514,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1602,6 +1533,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1631,10 +1563,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1651,6 +1580,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1680,10 +1610,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1702,6 +1629,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1731,10 +1659,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1751,6 +1676,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1780,10 +1706,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1802,6 +1725,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1831,10 +1755,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1851,6 +1772,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1880,10 +1802,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1902,6 +1821,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1931,10 +1851,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -1951,6 +1868,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -1980,10 +1898,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2002,6 +1917,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -2031,10 +1947,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2051,6 +1964,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -2080,10 +1994,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2102,6 +2013,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -2131,10 +2043,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2151,6 +2060,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -2180,10 +2090,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2193,12 +2100,63 @@ describe('given the load organization connections by user id function', () => { }) }) }) + describe('isAffiliated is set to true', () => { + it('returns organizations', async () => { + const connectionLoader = loadOrgConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + language: 'en', + i18n, + }) + + const connectionArgs = { + first: 5, + isAffiliated: true, + } + const orgs = await connectionLoader({ ...connectionArgs }) + + const orgLoader = loadOrgByKey({ query, language: 'en' }) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) + + expectedOrgs[0].id = expectedOrgs[0]._key + expectedOrgs[1].id = expectedOrgs[1]._key + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('organization', expectedOrgs[0]._key), + node: { + ...expectedOrgs[0], + }, + }, + { + cursor: toGlobalId('organization', expectedOrgs[1]._key), + node: { + ...expectedOrgs[1], + }, + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[1]._key), + }, + } + + expect(orgs).toEqual(expectedStructure) + }) + }) describe('isSuperAdmin is set to true', () => { it('returns organizations', async () => { const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -2210,10 +2168,7 @@ describe('given the load organization connections by user id function', () => { const orgs = await connectionLoader({ ...connectionArgs }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2237,10 +2192,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2254,15 +2206,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequiredBool: true }, language: 'en', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) const connectionArgs = { first: 5, @@ -2283,10 +2233,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[1]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2299,6 +2246,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 52, @@ -2346,16 +2294,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequiredBool: true }, language: 'en', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -2388,14 +2333,8 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'organization', - expectedOrgs[2]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[2]._key), }, } @@ -2408,16 +2347,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'en' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -2444,14 +2380,8 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2468,6 +2398,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -2516,15 +2447,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2548,10 +2477,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[1]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2565,15 +2491,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2597,10 +2521,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[0]._key), }, } @@ -2614,15 +2535,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2645,10 +2564,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[0]._key), }, } @@ -2662,15 +2578,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -2693,10 +2607,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[1]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -2709,6 +2620,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 51, @@ -2768,6 +2680,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -2797,10 +2710,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2817,6 +2727,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -2846,10 +2757,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2868,6 +2776,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -2897,10 +2806,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2917,6 +2823,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -2946,10 +2853,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -2968,6 +2872,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -2997,10 +2902,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3017,6 +2919,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3046,10 +2949,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3068,6 +2968,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3097,10 +2998,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3117,6 +3015,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3146,10 +3045,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3168,6 +3064,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3197,10 +3094,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3217,6 +3111,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3246,10 +3141,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3268,6 +3160,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3297,10 +3190,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3317,6 +3207,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3346,10 +3237,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3368,6 +3256,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3397,10 +3286,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3417,6 +3303,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3446,10 +3333,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3468,6 +3352,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3497,10 +3382,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3517,6 +3399,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3546,10 +3429,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3568,6 +3448,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3597,10 +3478,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3617,6 +3495,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3646,10 +3525,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3668,6 +3544,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3697,10 +3574,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3717,6 +3591,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3746,10 +3621,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3768,6 +3640,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3797,10 +3670,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3817,6 +3687,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3846,10 +3717,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3868,6 +3736,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3897,10 +3766,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3917,6 +3783,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3946,10 +3813,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -3968,6 +3832,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -3997,10 +3862,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4017,6 +3879,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4046,10 +3909,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4068,6 +3928,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4097,10 +3958,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4117,6 +3975,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4146,10 +4005,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4168,6 +4024,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4197,10 +4054,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4217,6 +4071,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4246,10 +4101,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4268,6 +4120,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4297,10 +4150,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4317,6 +4167,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4346,10 +4197,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'organization', - expectedOrg._key, - ), + startCursor: toGlobalId('organization', expectedOrg._key), endCursor: toGlobalId('organization', expectedOrg._key), }, } @@ -4383,6 +4231,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4425,6 +4274,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4461,15 +4311,13 @@ describe('given the load organization connections by user id function', () => { describe('search field is left empty', () => { it('returns unfiltered organizations', async () => { const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4501,14 +4349,8 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -4522,6 +4364,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4533,10 +4376,7 @@ describe('given the load organization connections by user id function', () => { const orgs = await connectionLoader({ ...connectionArgs }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) expectedOrgs[0].id = expectedOrgs[0]._key expectedOrgs[1].id = expectedOrgs[1]._key @@ -4560,10 +4400,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -4577,15 +4414,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequiredBool: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key]) const connectionArgs = { first: 5, @@ -4606,10 +4441,7 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[1]._key), endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -4622,6 +4454,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, + externalId: 'test', summaries: { web: { pass: 52, @@ -4669,16 +4502,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequiredBool: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -4711,14 +4541,8 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'organization', - expectedOrgs[2]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[2]._key), }, } @@ -4731,16 +4555,13 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) const orgLoader = loadOrgByKey({ query, language: 'fr' }) - const expectedOrgs = await orgLoader.loadMany([ - orgOne._key, - orgTwo._key, - saOrg._key, - ]) + const expectedOrgs = await orgLoader.loadMany([orgOne._key, orgTwo._key, saOrg._key]) const connectionArgs = { first: 5, @@ -4767,14 +4588,8 @@ describe('given the load organization connections by user id function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: false, - startCursor: toGlobalId( - 'organization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'organization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('organization', expectedOrgs[0]._key), + endCursor: toGlobalId('organization', expectedOrgs[1]._key), }, } @@ -4791,6 +4606,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -4840,6 +4656,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -4868,6 +4685,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -4900,6 +4718,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -4912,11 +4731,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `Organization` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `Organization` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -4930,6 +4745,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -4942,11 +4758,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `Organization` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `Organization` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -4962,6 +4774,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -4992,6 +4805,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -5020,13 +4834,12 @@ describe('given the load organization connections by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -5040,11 +4853,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: 123 attempted to have \`first\` set as a ${typeof invalidInput} for: loadOrgConnectionsByUserId.`, @@ -5054,13 +4863,12 @@ describe('given the load organization connections by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -5074,11 +4882,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: 123 attempted to have \`last\` set as a ${typeof invalidInput} for: loadOrgConnectionsByUserId.`, @@ -5091,16 +4895,13 @@ describe('given the load organization connections by user id function', () => { describe('given a database error', () => { describe('while querying domains', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query organizations. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query organizations. Please try again.')) const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -5113,9 +4914,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -5129,9 +4928,7 @@ describe('given the load organization connections by user id function', () => { it('returns an error message', async () => { const cursor = { next() { - throw new Error( - 'Unable to load organizations. Please try again.', - ) + throw new Error('Unable to load organizations. Please try again.') }, } const query = jest.fn().mockReturnValueOnce(cursor) @@ -5140,6 +4937,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, }) @@ -5152,9 +4950,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load organization(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -5186,6 +4982,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5214,6 +5011,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5246,6 +5044,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5259,9 +5058,7 @@ describe('given the load organization connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `Organization` ne peut être inférieure à zéro.', - ), + new Error('`first` sur la connexion `Organization` ne peut être inférieure à zéro.'), ) } @@ -5276,6 +5073,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5288,11 +5086,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `Organization` ne peut être inférieure à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `Organization` ne peut être inférieure à zéro.')) } expect(consoleOutput).toEqual([ @@ -5308,6 +5102,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5338,6 +5133,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5366,13 +5162,12 @@ describe('given the load organization connections by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5387,9 +5182,7 @@ describe('given the load organization connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -5400,13 +5193,12 @@ describe('given the load organization connections by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5421,9 +5213,7 @@ describe('given the load organization connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -5437,16 +5227,13 @@ describe('given the load organization connections by user id function', () => { describe('given a database error', () => { describe('while querying domains', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query organizations. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query organizations. Please try again.')) const connectionLoader = loadOrgConnectionsByUserId({ query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5459,11 +5246,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -5477,9 +5260,7 @@ describe('given the load organization connections by user id function', () => { it('returns an error message', async () => { const cursor = { next() { - throw new Error( - 'Unable to load organizations. Please try again.', - ) + throw new Error('Unable to load organizations. Please try again.') }, } const query = jest.fn().mockReturnValueOnce(cursor) @@ -5488,6 +5269,7 @@ describe('given the load organization connections by user id function', () => { query, userKey: 123, cleanseInput, + auth: { loginRequired: true }, language: 'fr', i18n, }) @@ -5500,11 +5282,7 @@ describe('given the load organization connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de charger l'organisation (s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api/src/organization/loaders/__tests__/load-organization-summaries-by-period.test.js b/api/src/organization/loaders/__tests__/load-organization-summaries-by-period.test.js new file mode 100644 index 0000000000..5ec899d0ad --- /dev/null +++ b/api/src/organization/loaders/__tests__/load-organization-summaries-by-period.test.js @@ -0,0 +1,49 @@ +import { loadOrganizationSummariesByPeriod } from '../load-organization-summaries-by-period' +import { createI18n } from '../../../create-i18n' + +describe('loadOrganizationSummariesByPeriod', () => { + let query, userKey, cleanseInput + + const i18n = createI18n('en') + + beforeEach(() => { + query = jest.fn() + userKey = '1' + cleanseInput = jest.fn((input) => input) + }) + + it('returns summaries for a given startDate and endDate', async () => { + const loader = loadOrganizationSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + const mockSummaries = [ + { _key: '1', date: '2023-01-01' }, + { _key: '2', date: '2023-01-02' }, + ] + query.mockResolvedValueOnce({ + all: jest.fn().mockResolvedValueOnce(mockSummaries), + }) + + const result = await loader({ orgId: 'org1', startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' }) + + expect(result).toEqual(mockSummaries) + }) + + it('handles database errors gracefully', async () => { + const loader = loadOrganizationSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + query.mockRejectedValueOnce(new Error('Database error')) + + await expect( + loader({ orgId: 'org1', startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' }), + ).rejects.toThrow('Unable to load organization summary data. Please try again.') + }) + + it('handles cursor errors gracefully', async () => { + const loader = loadOrganizationSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + query.mockResolvedValueOnce({ + all: jest.fn().mockRejectedValueOnce(new Error('Cursor error')), + }) + + await expect( + loader({ orgId: 'org1', startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' }), + ).rejects.toThrow('Unable to load organization summary data. Please try again.') + }) +}) diff --git a/api/src/organization/loaders/index.js b/api/src/organization/loaders/index.js new file mode 100644 index 0000000000..82b0be23f5 --- /dev/null +++ b/api/src/organization/loaders/index.js @@ -0,0 +1,8 @@ +export * from './load-organization-by-key' +export * from './load-organization-by-slug' +export * from './load-organization-connections-by-domain-id' +export * from './load-organization-connections-by-user-id' +export * from './load-all-organization-domain-statuses' +export * from './load-organization-domain-statuses' +export * from './load-organization-summaries-by-period' +export * from './load-organization-names-by-id' diff --git a/api/src/organization/loaders/load-all-organization-domain-statuses.js b/api/src/organization/loaders/load-all-organization-domain-statuses.js new file mode 100644 index 0000000000..5910cb48d5 --- /dev/null +++ b/api/src/organization/loaders/load-all-organization-domain-statuses.js @@ -0,0 +1,168 @@ +import { t } from '@lingui/macro' +import { aql } from 'arangojs' + +export const loadAllOrganizationDomainStatuses = + ({ query, userKey, i18n, language }) => + async ({ filters }) => { + let domains + let domainFilters = aql`` + let archivedFilter = aql`FILTER d.archived != true` + if (typeof filters !== 'undefined') { + filters.forEach(({ filterCategory, comparison, filterValue }) => { + if (comparison === '==') { + comparison = aql`==` + } else { + comparison = aql`!=` + } + if (filterCategory === 'dmarc-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.dmarc ${comparison} ${filterValue} + ` + } else if (filterCategory === 'dkim-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.dkim ${comparison} ${filterValue} + ` + } else if (filterCategory === 'https-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.https ${comparison} ${filterValue} + ` + } else if (filterCategory === 'spf-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.spf ${comparison} ${filterValue} + ` + } else if (filterCategory === 'ciphers-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.ciphers ${comparison} ${filterValue} + ` + } else if (filterCategory === 'curves-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.curves ${comparison} ${filterValue} + ` + } else if (filterCategory === 'hsts-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.hsts ${comparison} ${filterValue} + ` + } else if (filterCategory === 'protocols-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.protocols ${comparison} ${filterValue} + ` + } else if (filterCategory === 'certificates-status') { + domainFilters = aql` + ${domainFilters} + FILTER d.status.certificates ${comparison} ${filterValue} + ` + } else if (filterCategory === 'tags') { + if (filterValue === 'nxdomain') { + domainFilters = aql` + ${domainFilters} + FILTER d.rcode ${comparison} "NXDOMAIN" + ` + } else if (filterValue === 'blocked') { + domainFilters = aql` + ${domainFilters} + FILTER d.blocked ${comparison} true + ` + } else if (filterValue === 'wildcard-sibling') { + domainFilters = aql` + ${domainFilters} + FILTER d.wildcardSibling ${comparison} true + ` + } else if (filterValue === 'wildcard-entry') { + domainFilters = aql` + ${domainFilters} + FILTER d.wildcardEntry ${comparison} true + ` + } else if (filterValue === 'scan-pending') { + domainFilters = aql`${domainFilters}` + } else if (filterValue === 'has-entrust-certificate') { + domainFilters = aql` + ${domainFilters} + FILTER d.hasEntrustCertificate ${comparison} true + ` + } else if (filterValue === 'cve-detected') { + domainFilters = aql` + ${domainFilters} + FILTER d.cveDetected ${comparison} true + ` + } else if (filterValue === 'archived') { + archivedFilter = aql`FILTER d.archived ${comparison} true` + } + } else if (filterCategory === 'dmarc-phase') { + domainFilters = aql` + ${domainFilters} + FILTER v.phase ${comparison} ${filterValue} + ` + } + }) + } + + try { + domains = ( + await query` + WITH domains + FOR d IN domains + ${archivedFilter} + ${domainFilters} + LET ipAddresses = FIRST( + FILTER d.latestDnsScan + LET latestDnsScan = DOCUMENT(d.latestDnsScan) + FILTER latestDnsScan.resolveIps + RETURN latestDnsScan.resolveIps + ) + LET vulnerabilities = ( + FOR finding IN additionalFindings + FILTER finding.domain == d._id + LIMIT 1 + RETURN UNIQUE( + FOR wc IN finding.webComponents + FILTER LENGTH(wc.WebComponentCves) > 0 + FOR vuln IN wc.WebComponentCves + FILTER vuln.Cve NOT IN (d.ignoredCves || []) + RETURN vuln.Cve + ) + )[0] + LET verifiedOrgs = ( + FOR v,e IN 1..1 INBOUND d._id claims + FILTER v.verified == true + RETURN MERGE({ externalId: v.externalId || null }, TRANSLATE(${language}, v.orgDetails)) + ) + RETURN { + "domain": d.domain, + "orgNames": verifiedOrgs[*].name, + "orgAcronyms": verifiedOrgs[*].acronym, + "orgExternalIDs": verifiedOrgs[*].externalId, + "ipAddresses": ipAddresses, + "https": d.status.https, + "hsts": d.status.hsts, + "certificates": d.status.certificates, + "ciphers": d.status.ciphers, + "curves": d.status.curves, + "protocols": d.status.protocols, + "spf": d.status.spf, + "dkim": d.status.dkim, + "dmarc": d.status.dmarc, + "phase": d.phase, + "rcode": d.rcode, + "blocked": d.blocked, + "wildcardSibling": d.wildcardSibling, + "wildcardEntry": d.wildcardEntry, + "hasEntrustCertificate": d.hasEntrustCertificate, + "top25Vulnerabilities": vulnerabilities + } + ` + ).all() + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadOrganizationDomainStatuses: ${err}`) + throw new Error(i18n._(t`Unable to load organization domain statuses. Please try again.`)) + } + + return domains + } diff --git a/api/src/organization/loaders/load-organization-by-key.js b/api/src/organization/loaders/load-organization-by-key.js new file mode 100644 index 0000000000..ee76ff782a --- /dev/null +++ b/api/src/organization/loaders/load-organization-by-key.js @@ -0,0 +1,45 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadOrgByKey = ({ query, language, userKey, i18n }) => + new DataLoader(async (ids) => { + let cursor + + try { + cursor = await query` + WITH claims, domains, organizations + FOR org IN organizations + FILTER org._key IN ${ids} + LET orgDomains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) + RETURN MERGE( + { + _id: org._id, + _key: org._key, + _rev: org._rev, + _type: "organization", + id: org._key, + verified: org.verified, + externalId: org.externalId, + domainCount: COUNT(orgDomains), + summaries: org.summaries + }, + TRANSLATE(${language}, org.orgDetails) + ) + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadOrgByKey: ${err}`) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) + } + + const orgMap = {} + try { + await cursor.forEach((org) => { + orgMap[org._key] = org + }) + } catch (err) { + console.error(`Cursor error occurred when user: ${userKey} during loadOrgByKey: ${err}`) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) + } + + return ids.map((id) => orgMap[id]) + }) diff --git a/api/src/organization/loaders/load-organization-by-slug.js b/api/src/organization/loaders/load-organization-by-slug.js new file mode 100644 index 0000000000..27ca324452 --- /dev/null +++ b/api/src/organization/loaders/load-organization-by-slug.js @@ -0,0 +1,49 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadOrgBySlug = ({ query, language, userKey, i18n }) => + new DataLoader(async (slugs) => { + let cursor + + try { + cursor = await query` + WITH claims, domains, organizations + FOR org IN organizations + FILTER org.orgDetails.en.slug IN ${slugs} + OR org.orgDetails.fr.slug IN ${slugs} + LET orgDomains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) + RETURN MERGE( + { + _id: org._id, + _key: org._key, + _rev: org._rev, + _type: "organization", + id: org._key, + verified: org.verified, + externalId: org.externalId, + domainCount: COUNT(orgDomains), + summaries: org.summaries, + slugEN: org.orgDetails.en.slug, + slugFR: org.orgDetails.fr.slug + }, + TRANSLATE(${language}, org.orgDetails) + ) + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadOrgBySlug: ${err}`) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) + } + + const orgMap = {} + try { + await cursor.forEach((org) => { + orgMap[org.slugEN] = org + orgMap[org.slugFR] = org + }) + } catch (err) { + console.error(`Cursor error occurred when user: ${userKey} running loadOrgBySlug: ${err}`) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) + } + + return slugs.map((slug) => orgMap[slug]) + }) diff --git a/api-js/src/organization/loaders/load-organization-connections-by-domain-id.js b/api/src/organization/loaders/load-organization-connections-by-domain-id.js similarity index 89% rename from api-js/src/organization/loaders/load-organization-connections-by-domain-id.js rename to api/src/organization/loaders/load-organization-connections-by-domain-id.js index 4e61205596..50a84fc55a 100644 --- a/api-js/src/organization/loaders/load-organization-connections-by-domain-id.js +++ b/api/src/organization/loaders/load-organization-connections-by-domain-id.js @@ -3,19 +3,8 @@ import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' export const loadOrgConnectionsByDomainId = - ({ query, language, userKey, cleanseInput, i18n }) => - async ({ - domainId, - after, - before, - first, - last, - orderBy, - search, - isSuperAdmin, - isAdmin, - includeSuperAdminOrg, - }) => { + ({ query, language, userKey, cleanseInput, i18n, auth: { loginRequiredBool } }) => + async ({ domainId, after, before, first, last, orderBy, search, isSuperAdmin, isAdmin, includeSuperAdminOrg }) => { const userDBId = `users/${userKey}` let afterTemplate = aql`` @@ -174,18 +163,14 @@ export const loadOrgConnectionsByDomainId = `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadOrgConnectionsByDomainId.`, ) throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Organization\` connection.`, - ), + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Organization\` connection.`), ) } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadOrgConnectionsByDomainId.`, ) throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Organization\` connection is not supported.`, - ), + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Organization\` connection is not supported.`), ) } else if (typeof first === 'number' || typeof last === 'number') { /* istanbul ignore else */ @@ -194,17 +179,11 @@ export const loadOrgConnectionsByDomainId = console.warn( `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadOrgConnectionsByDomainId.`, ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Organization\` connection cannot be less than zero.`, - ), - ) + throw new Error(i18n._(t`\`${argSet}\` on the \`Organization\` connection cannot be less than zero.`)) } else if (first > 100 || last > 100) { const argSet = typeof first !== 'undefined' ? 'first' : 'last' const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadOrgConnectionsByDomainId.`, - ) + console.warn(`User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadOrgConnectionsByDomainId.`) throw new Error( i18n._( t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, @@ -221,9 +200,7 @@ export const loadOrgConnectionsByDomainId = console.warn( `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadOrgConnectionsByDomainId.`, ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) } let hasNextPageFilter = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)` @@ -369,7 +346,18 @@ export const loadOrgConnectionsByDomainId = } let orgKeysQuery - if (isSuperAdmin) { + if (!loginRequiredBool) { + orgKeysQuery = aql` + WITH affiliations, claims, domains, organizations, organizationSearch, users + LET keys = ( + FOR org IN organizations + ${includeSuperAdminOrg && isSuperAdmin ? aql`` : includeSuperAdminOrgQuery} + RETURN org._key + ) + LET claimKeys = (FOR v, e IN 1..1 INBOUND ${domainId} claims RETURN v._key) + LET orgKeys = INTERSECTION(keys, claimKeys) + ` + } else if (isSuperAdmin) { orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users LET keys = ( @@ -386,8 +374,7 @@ export const loadOrgConnectionsByDomainId = LET keys = ( FOR org, e IN 1..1 INBOUND ${userDBId} affiliations - FILTER e.permission == "admin" - OR e.permission == "super_admin" + FILTER e.permission IN ["admin", "owner", "super_admin"] ${includeSuperAdminOrgQuery} RETURN org._key ) @@ -415,29 +402,37 @@ export const loadOrgConnectionsByDomainId = search = cleanseInput(search) orgQuery = aql` LET tokenArrEN = TOKENS(${search}, "text_en") - LET searchedOrgsEN = FLATTEN(UNIQUE( + LET searchedOrgsKeysCountEN = FLATTEN( FOR token IN tokenArrEN FOR org IN organizationSearch SEARCH ANALYZER( - org.orgDetails.en.acronym LIKE CONCAT("%", token, "%") - OR org.orgDetails.fr.acronym LIKE CONCAT("%", token, "%") - OR org.orgDetails.en.name LIKE CONCAT("%", token, "%") - OR org.orgDetails.fr.name LIKE CONCAT("%", token, "%") + org.orgDetails.en.acronym LIKE CONCAT("%", token, "%") + OR org.orgDetails.en.name LIKE CONCAT("%", token, "%") , "text_en") FILTER org._key IN orgKeys - RETURN org._key - )) + COLLECT orgKey = org._key WITH COUNT INTO orgCount + RETURN { + 'orgKey': orgKey, + 'orgCount': orgCount + } + ) + LET searchedOrgsEN = searchedOrgsKeysCountEN[* FILTER CURRENT.orgCount == LENGTH(tokenArrEN)].orgKey LET tokenArrFR = TOKENS(${search}, "text_fr") - LET searchedOrgsFR = FLATTEN(UNIQUE( + LET searchedOrgsKeysCountFR = FLATTEN( FOR token IN tokenArrFR - FOR org IN organizationSearch - SEARCH ANALYZER( + FOR org IN organizationSearch + SEARCH ANALYZER( org.orgDetails.fr.acronym LIKE CONCAT("%", token, "%") OR org.orgDetails.fr.name LIKE CONCAT("%", token, "%") - , "text_fr") - FILTER org._key IN orgKeys - RETURN org._key - )) + , "text_fr") + FILTER org._key IN orgKeys + COLLECT orgKey = org._key WITH COUNT INTO orgCount + RETURN { + 'orgKey': orgKey, + 'orgCount': orgCount + } + ) + LET searchedOrgsFR = searchedOrgsKeysCountFR[* FILTER CURRENT.orgCount == LENGTH(tokenArrFR)].orgKey LET searchedOrgs = UNION_DISTINCT(searchedOrgsEN, searchedOrgsFR) ` filterString = aql`FILTER org._key IN searchedOrgs` @@ -458,7 +453,7 @@ export const loadOrgConnectionsByDomainId = FOR org IN organizations ${filterString} LET orgDomains = (FOR v, e IN 1..1 OUTBOUND org._id claims RETURN e._to) - ${afterTemplate} + ${afterTemplate} ${beforeTemplate} SORT ${sortByField} @@ -471,9 +466,10 @@ export const loadOrgConnectionsByDomainId = _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(orgDomains), - summaries: org.summaries - }, + summaries: org.summaries + }, TRANSLATE(${language}, org.orgDetails) ) ) @@ -486,7 +482,7 @@ export const loadOrgConnectionsByDomainId = SORT ${sortByField} TO_NUMBER(org._key) ${sortString} LIMIT 1 RETURN org ) > 0 ? true : false) - + LET hasPreviousPage = (LENGTH( FOR org IN organizations ${filterString} @@ -495,23 +491,21 @@ export const loadOrgConnectionsByDomainId = SORT ${sortByField} TO_NUMBER(org._key) ${sortString} LIMIT 1 RETURN org ) > 0 ? true : false) - - RETURN { + + RETURN { "organizations": retrievedOrgs, "totalCount": ${totalCount}, - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedOrgs)._key, - "endKey": LAST(retrievedOrgs)._key + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedOrgs)._key, + "endKey": LAST(retrievedOrgs)._key } ` } catch (err) { console.error( `Database error occurred while user: ${userKey} was trying to gather orgs in loadOrgConnectionsByDomainId, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) } let organizationInfo @@ -521,9 +515,7 @@ export const loadOrgConnectionsByDomainId = console.error( `Cursor error occurred while user: ${userKey} was trying to gather orgs in loadOrgConnectionsByDomainId, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) } if (organizationInfo.organizations.length === 0) { diff --git a/api-js/src/organization/loaders/load-organization-connections-by-user-id.js b/api/src/organization/loaders/load-organization-connections-by-user-id.js similarity index 85% rename from api-js/src/organization/loaders/load-organization-connections-by-user-id.js rename to api/src/organization/loaders/load-organization-connections-by-user-id.js index f4a0750b48..c1383a7445 100644 --- a/api-js/src/organization/loaders/load-organization-connections-by-user-id.js +++ b/api/src/organization/loaders/load-organization-connections-by-user-id.js @@ -3,7 +3,7 @@ import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' export const loadOrgConnectionsByUserId = - ({ query, userKey, cleanseInput, language, i18n }) => + ({ query, userKey, cleanseInput, language, i18n, auth: { loginRequiredBool } }) => async ({ after, before, @@ -14,6 +14,8 @@ export const loadOrgConnectionsByUserId = search, isAdmin, includeSuperAdminOrg, + isVerified, + isAffiliated, }) => { const userDBId = `users/${userKey}` @@ -173,37 +175,25 @@ export const loadOrgConnectionsByUserId = `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadOrgConnectionsByUserId.`, ) throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Organization\` connection.`, - ), + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Organization\` connection.`), ) } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadOrgConnectionsByUserId.`, ) throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Organization\` connection is not supported.`, - ), + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Organization\` connection is not supported.`), ) } else if (typeof first === 'number' || typeof last === 'number') { /* istanbul ignore else */ if (first < 0 || last < 0) { const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadOrgConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Organization\` connection cannot be less than zero.`, - ), - ) + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadOrgConnectionsByUserId.`) + throw new Error(i18n._(t`\`${argSet}\` on the \`Organization\` connection cannot be less than zero.`)) } else if (first > 100 || last > 100) { const argSet = typeof first !== 'undefined' ? 'first' : 'last' const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadOrgConnectionsByUserId.`, - ) + console.warn(`User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadOrgConnectionsByUserId.`) throw new Error( i18n._( t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, @@ -220,9 +210,7 @@ export const loadOrgConnectionsByUserId = console.warn( `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadOrgConnectionsByUserId.`, ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) } let hasNextPageFilter = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)` @@ -367,36 +355,78 @@ export const loadOrgConnectionsByUserId = includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` } + let isVerifiedQuery = aql`` + if (isVerified) { + isVerifiedQuery = aql`FILTER org.verified == true` + } + + let isAdminFilter = aql`` + if (isAdmin) { + isAdminFilter = aql`FILTER e.permission IN ["admin", "owner", "super_admin"]` + } + let orgKeysQuery if (isSuperAdmin) { orgKeysQuery = aql` WITH claims, domains, organizations, organizationSearch LET orgKeys = ( FOR org IN organizations + ${isVerifiedQuery} ${includeSuperAdminOrgQuery} RETURN org._key ) ` - } else if (isAdmin) { + } else if (isAffiliated) { orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) LET orgKeys = ( - FOR org, e IN 1..1 - INBOUND ${userDBId} affiliations - FILTER e.permission == "admin" - OR e.permission == "super_admin" - ${includeSuperAdminOrgQuery} - RETURN org._key + FOR org IN organizations + ${isVerifiedQuery} + ${includeSuperAdminOrgQuery} + FILTER org._key IN userAffiliations[*]._key + RETURN org._key ) ` + } else if (!loginRequiredBool) { + orgKeysQuery = aql` + WITH affiliations, claims, domains, organizations, organizationSearch, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + ${isAdminFilter} + RETURN v + ) + LET orgKeys = ( + FOR org IN organizations + ${isVerifiedQuery} + ${includeSuperAdminOrgQuery} + FILTER org._key IN userAffiliations[*]._key ${isAdmin ? aql`` : aql`|| org.verified == true`} + RETURN org._key + ) + ` } else { orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + ${isAdminFilter} + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) LET orgKeys = ( - FOR org, e IN 1..1 - INBOUND ${userDBId} affiliations - ${includeSuperAdminOrgQuery} - RETURN org._key + FOR org IN organizations + ${isVerifiedQuery} + ${includeSuperAdminOrgQuery} + FILTER org._key IN userAffiliations[*]._key ${ + isAdmin ? aql`` : aql`|| (org.verified == true && hasVerifiedOrgAffiliation == true)` + } + RETURN org._key ) ` } @@ -408,7 +438,7 @@ export const loadOrgConnectionsByUserId = search = cleanseInput(search) orgQuery = aql` LET tokenArrEN = TOKENS(${search}, "text_en") - LET searchedOrgsEN = FLATTEN(UNIQUE( + LET searchedOrgsKeysCountEN = FLATTEN( FOR token IN tokenArrEN FOR org IN organizationSearch SEARCH ANALYZER( @@ -416,10 +446,15 @@ export const loadOrgConnectionsByUserId = OR org.orgDetails.en.name LIKE CONCAT("%", token, "%") , "text_en") FILTER org._key IN orgKeys - RETURN org._key - )) + COLLECT orgKey = org._key WITH COUNT INTO orgCount + RETURN { + 'orgKey': orgKey, + 'orgCount': orgCount + } + ) + LET searchedOrgsEN = searchedOrgsKeysCountEN[* FILTER CURRENT.orgCount == LENGTH(tokenArrEN)].orgKey LET tokenArrFR = TOKENS(${search}, "text_fr") - LET searchedOrgsFR = FLATTEN(UNIQUE( + LET searchedOrgsKeysCountFR = FLATTEN( FOR token IN tokenArrFR FOR org IN organizationSearch SEARCH ANALYZER( @@ -427,8 +462,13 @@ export const loadOrgConnectionsByUserId = OR org.orgDetails.fr.name LIKE CONCAT("%", token, "%") , "text_fr") FILTER org._key IN orgKeys - RETURN org._key - )) + COLLECT orgKey = org._key WITH COUNT INTO orgCount + RETURN { + 'orgKey': orgKey, + 'orgCount': orgCount + } + ) + LET searchedOrgsFR = searchedOrgsKeysCountFR[* FILTER CURRENT.orgCount == LENGTH(tokenArrFR)].orgKey LET searchedOrgs = UNION_DISTINCT(searchedOrgsEN, searchedOrgsFR) ` filterString = aql`FILTER org._key IN searchedOrgs` @@ -455,16 +495,17 @@ export const loadOrgConnectionsByUserId = ${sortByField} ${limitTemplate} RETURN MERGE( - { + { _id: org._id, _key: org._key, _rev: org._rev, _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(orgDomains), - summaries: org.summaries - }, + summaries: org.summaries + }, TRANSLATE(${language}, org.orgDetails) ) ) @@ -477,7 +518,7 @@ export const loadOrgConnectionsByUserId = SORT ${sortByField} TO_NUMBER(org._key) ${sortString} LIMIT 1 RETURN org ) > 0 ? true : false) - + LET hasPreviousPage = (LENGTH( FOR org IN organizations ${filterString} @@ -486,23 +527,21 @@ export const loadOrgConnectionsByUserId = SORT ${sortByField} TO_NUMBER(org._key) ${sortString} LIMIT 1 RETURN org ) > 0 ? true : false) - + RETURN { "organizations": retrievedOrgs, "totalCount": ${totalCount}, - "hasNextPage": hasNextPage, - "hasPreviousPage": hasPreviousPage, - "startKey": FIRST(retrievedOrgs)._key, - "endKey": LAST(retrievedOrgs)._key + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedOrgs)._key, + "endKey": LAST(retrievedOrgs)._key } ` } catch (err) { console.error( `Database error occurred while user: ${userKey} was trying to query organizations in loadOrgConnectionsByUserId, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) } let organizationInfo @@ -512,9 +551,7 @@ export const loadOrgConnectionsByUserId = console.error( `Cursor error occurred while user: ${userKey} was trying to gather organizations in loadOrgConnectionsByUserId, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) } if (organizationInfo.organizations.length === 0) { diff --git a/api/src/organization/loaders/load-organization-domain-statuses.js b/api/src/organization/loaders/load-organization-domain-statuses.js new file mode 100644 index 0000000000..f0009becb3 --- /dev/null +++ b/api/src/organization/loaders/load-organization-domain-statuses.js @@ -0,0 +1,176 @@ +import { t } from '@lingui/macro' +import { aql } from 'arangojs' + +export const loadOrganizationDomainStatuses = + ({ query, userKey, i18n }) => + async ({ orgId, filters }) => { + let domains + let domainFilters = aql`` + let archivedFilter = aql`FILTER v.archived != true` + if (typeof filters !== 'undefined') { + filters.forEach(({ filterCategory, comparison, filterValue }) => { + if (comparison === '==') { + comparison = aql`==` + } else { + comparison = aql`!=` + } + if (filterCategory === 'dmarc-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.dmarc ${comparison} ${filterValue} + ` + } else if (filterCategory === 'dkim-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.dkim ${comparison} ${filterValue} + ` + } else if (filterCategory === 'https-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.https ${comparison} ${filterValue} + ` + } else if (filterCategory === 'spf-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.spf ${comparison} ${filterValue} + ` + } else if (filterCategory === 'ciphers-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.ciphers ${comparison} ${filterValue} + ` + } else if (filterCategory === 'curves-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.curves ${comparison} ${filterValue} + ` + } else if (filterCategory === 'hsts-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.hsts ${comparison} ${filterValue} + ` + } else if (filterCategory === 'protocols-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.protocols ${comparison} ${filterValue} + ` + } else if (filterCategory === 'certificates-status') { + domainFilters = aql` + ${domainFilters} + FILTER v.status.certificates ${comparison} ${filterValue} + ` + } else if (filterCategory === 'tags') { + if (filterValue === 'nxdomain') { + domainFilters = aql` + ${domainFilters} + FILTER v.rcode ${comparison} "NXDOMAIN" + ` + } else if (filterValue === 'blocked') { + domainFilters = aql` + ${domainFilters} + FILTER v.blocked ${comparison} true + ` + } else if (filterValue === 'wildcard-sibling') { + domainFilters = aql` + ${domainFilters} + FILTER v.wildcardSibling ${comparison} true + ` + } else if (filterValue === 'wildcard-entry') { + domainFilters = aql` + ${domainFilters} + FILTER v.wildcardEntry ${comparison} true + ` + } else if (filterValue === 'has-entrust-certificate') { + domainFilters = aql` + ${domainFilters} + FILTER v.hasEntrustCertificate ${comparison} true + ` + } else if (filterValue === 'cve-detected') { + domainFilters = aql` + ${domainFilters} + FILTER v.cveDetected ${comparison} true + ` + } else if (filterValue === 'cvd-enrolled') { + domainFilters = aql` + ${domainFilters} + FILTER v.cvdEnrollment.status ${comparison} "enrolled" + ` + } else if (filterValue === 'scan-pending') { + domainFilters = aql`${domainFilters}` + } else if (filterValue === 'archived') { + archivedFilter = aql`FILTER v.archived ${comparison} true` + } else { + domainFilters = aql` + ${domainFilters} + FILTER POSITION(e.tags, ${filterValue}) ${comparison} true + ` + } + } else if (filterCategory === 'asset-state') { + domainFilters = aql` + ${domainFilters} + FILTER e.assetState ${comparison} ${filterValue} + ` + } else if (filterCategory === 'guidance-tag') { + domainFilters = aql` + ${domainFilters} + FILTER POSITION(negativeTags, ${filterValue}) ${comparison} true + ` + } else if (filterCategory === 'dmarc-phase') { + domainFilters = aql` + ${domainFilters} + FILTER v.phase ${comparison} ${filterValue} + ` + } + }) + } + + try { + domains = ( + await query` + WITH claims, domains, organizations + FOR v, e IN 1..1 OUTBOUND ${orgId} claims + ${archivedFilter} + LET negativeTags = APPEND(v.negativeTags.dns, v.negativeTags.web) + ${domainFilters} + LET ipAddresses = FIRST( + FILTER v.latestDnsScan + LET latestDnsScan = DOCUMENT(v.latestDnsScan) + FILTER latestDnsScan.resolveIps + RETURN latestDnsScan.resolveIps + ) + LET vulnerabilities = ( + FOR finding IN additionalFindings + FILTER finding.domain == v._id + LIMIT 1 + RETURN UNIQUE( + FOR wc IN finding.webComponents + FILTER LENGTH(wc.WebComponentCves) > 0 + FOR vuln IN wc.WebComponentCves + FILTER vuln.Cve NOT IN (v.ignoredCves || []) + RETURN vuln.Cve + ) + )[0] + RETURN { + domain: v.domain, + ipAddresses: ipAddresses, + status: v.status, + phase: v.phase, + tags: e.tags, + assetState: e.assetState, + rcode: v.rcode, + blocked: v.blocked, + wildcardSibling: v.wildcardSibling, + wildcardEntry: v.wildcardEntry, + hasEntrustCertificate: v.hasEntrustCertificate, + top25Vulnerabilities: vulnerabilities, + cvdEnrollmentStatus: v.cvdEnrollment.status + } + ` + ).all() + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadOrganizationDomainStatuses: ${err}`) + throw new Error(i18n._(t`Unable to load organization domain statuses. Please try again.`)) + } + + return domains + } diff --git a/api/src/organization/loaders/load-organization-names-by-id.js b/api/src/organization/loaders/load-organization-names-by-id.js new file mode 100644 index 0000000000..037ff5a474 --- /dev/null +++ b/api/src/organization/loaders/load-organization-names-by-id.js @@ -0,0 +1,33 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadOrganizationNamesById = ({ query, userKey, i18n }) => + new DataLoader(async (ids) => { + let cursor + try { + cursor = await query` + FOR orgId IN ${ids} + LET org = DOCUMENT(organizations, orgId) + RETURN { + orgId, + orgNameEN: org.orgDetails.en.name, + orgNameFR: org.orgDetails.fr.name, + } + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadOrganizationNamesById: ${err}`) + throw new Error(i18n._(t`Unable to load organization names. Please try again.`)) + } + + const orgMap = {} + try { + await cursor.forEach((org) => { + orgMap[org.orgId] = org + }) + } catch (err) { + console.error(`Cursor error occurred when user: ${userKey} during loadOrganizationNamesById: ${err}`) + throw new Error(i18n._(t`Unable to load organization names. Please try again.`)) + } + + return ids.map((id) => orgMap[id]) + }) diff --git a/api/src/organization/loaders/load-organization-summaries-by-period.js b/api/src/organization/loaders/load-organization-summaries-by-period.js new file mode 100644 index 0000000000..5a2105439f --- /dev/null +++ b/api/src/organization/loaders/load-organization-summaries-by-period.js @@ -0,0 +1,70 @@ +import { t } from '@lingui/macro' +import { aql } from 'arangojs' + +export const loadOrganizationSummariesByPeriod = + ({ query, userKey, cleanseInput, i18n }) => + async ({ orgId, startDate, endDate, sortDirection = 'ASC', limit }) => { + const cleansedStartDate = startDate ? cleanseInput(startDate) : null + const cleansedEndDate = endDate ? cleanseInput(endDate) : new Date().toISOString() + + const filterUniqueDates = (array) => { + const filteredArray = [] + const dateSet = new Set() + array.forEach((item) => { + if (!dateSet.has(item.date)) { + filteredArray.push(item) + dateSet.add(item.date) + } + }) + return filteredArray + } + + const sortString = aql`SORT summary.date ${sortDirection}` + let startDateFilter = aql`` + if (typeof cleansedStartDate !== 'undefined') { + startDateFilter = aql`FILTER DATE_FORMAT(summary.date, '%yyyy-%mm-%dd') >= DATE_FORMAT(${cleansedStartDate}, '%yyyy-%mm-%dd')` + } + let endDateFilter = aql`` + if (typeof cleansedEndDate !== 'undefined') { + endDateFilter = aql`FILTER DATE_FORMAT(summary.date, '%yyyy-%mm-%dd') <= DATE_FORMAT(${cleansedEndDate}, '%yyyy-%mm-%dd')` + } + let limitString = aql`` + if (typeof limit !== 'undefined') { + limitString = aql`LIMIT ${limit}` + } + + let requestedSummaryInfo + try { + requestedSummaryInfo = await query` + LET latestSummary = (RETURN DOCUMENT(organizations, ${orgId}).summaries) + LET historicalSummaries = ( + FOR summary IN organizationSummaries + FILTER summary.organization == ${orgId} + ${startDateFilter} + ${endDateFilter} + RETURN summary + ) + FOR summary IN APPEND(latestSummary, historicalSummaries) + ${sortString} + ${limitString} + RETURN summary + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather organization summaries in loadOrganizationSummariesByPeriod, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load organization summary data. Please try again.`)) + } + + let summariesInfo + try { + summariesInfo = filterUniqueDates(await requestedSummaryInfo.all()) + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather organization summaries in loadOrganizationSummariesByPeriod, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load organization summary data. Please try again.`)) + } + + return summariesInfo || [] + } diff --git a/api/src/organization/mutations/__tests__/archive-organization.test.js b/api/src/organization/mutations/__tests__/archive-organization.test.js new file mode 100644 index 0000000000..e574daa6ff --- /dev/null +++ b/api/src/organization/mutations/__tests__/archive-organization.test.js @@ -0,0 +1,518 @@ +import { setupI18n } from '@lingui/core' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { OrganizationDataSource } from '../../data-source' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('archiving an organization', () => { + let query, drop, truncate, schema, collections, transaction, user, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful archival', () => { + let org, domain + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + }) + }) + afterEach(async () => { + await truncate() + await drop() + }) + describe('users permission is super admin', () => { + describe('org is verified', () => { + beforeEach(async () => { + org = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + const superAdminOrg = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + }, + }, + }) + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + + describe('org is the only one claiming the domain', () => { + it('archives the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + archiveOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck.archived).toEqual(true) + }) + }) + describe('multiple orgs claim the domain', () => { + beforeEach(async () => { + const secondOrg = await collections.organizations.save({}) + await collections.claims.save({ + _from: secondOrg._id, + _to: domain._id, + }) + }) + it('does not archive the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + archiveOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck.archived).toEqual(false || undefined) + }) + }) + }) + }) + }) + describe('given an unsuccessful archival', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('the requested org is undefined', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + archiveOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue(undefined), + } } }, + }, + }) + + const expectedResponse = { + data: { + archiveOrganization: { + result: { + code: 400, + description: 'Unable to archive unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to archive org: 123, but there is no org associated with that id.`, + ]) + }) + }) + describe('given an incorrect permission', () => { + describe('users belong to the org', () => { + describe('users role is admin', () => { + describe('user attempts to archive an org', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + archiveOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + } } }, + }, + }) + + const expectedResponse = { + data: { + archiveOrganization: { + result: { + code: 403, + description: + 'Permission Denied: Please contact super admin for help with archiving organization.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to archive org: 123, however they do not have the correct permission level. Permission: admin`, + ]) + }) + }) + }) + }) + }) + describe('given a data source error', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + archiveOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('super_admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { + organization: { + byKey: { + load: jest.fn().mockReturnValue({ + _id: 'organizations/123', + _key: 123, + verified: false, + slug: 'treasury-board-secretariat', + name: 'Treasury Board of Canada Secretariat', + }), + }, + archive: jest.fn().mockRejectedValue(new Error('Unable to archive organization. Please try again.')), + }, + }, + }, + }) + + const error = [new GraphQLError('Unable to archive organization. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) + }) + }) + }) + }) +}) diff --git a/api/src/organization/mutations/__tests__/create-organization.test.js b/api/src/organization/mutations/__tests__/create-organization.test.js new file mode 100644 index 0000000000..3ab87077a2 --- /dev/null +++ b/api/src/organization/mutations/__tests__/create-organization.test.js @@ -0,0 +1,564 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput, slugify } from '../../../validators' +import { checkSuperAdmin, superAdminRequired, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { OrganizationDataSource } from '../../data-source' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env + +describe('create an organization', () => { + let query, drop, truncate, schema, collections, transaction, user, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful org creation', () => { + beforeEach(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('language is set to english', () => { + it('returns the organizations information', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createOrganization( + input: { + acronymEN: "TBS" + acronymFR: "SCT" + nameEN: "Treasury Board of Canada Secretariat" + nameFR: "Secrétariat du Conseil Trésor du Canada" + externalId: "EXT123" + verified: false + } + ) { + result { + ... on Organization { + id + acronym + slug + name + verified + externalId + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'en', + ip: '1.2.3.4', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + checkSuperAdmin: checkSuperAdmin({ i18n, query, userKey: user._key }), + superAdminRequired: superAdminRequired({ i18n }), + }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + validators: { + cleanseInput, + slugify, + }, + }, + }) + + const orgCursor = await query` + FOR org IN organizations + FILTER (LOWER("treasury-board-of-canada-secretariat") == LOWER(TRANSLATE("en", org.orgDetails).slug)) + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, verified: org.verified }, TRANSLATE("en", org.orgDetails)) + ` + + const org = await orgCursor.next() + + const expectedResponse = { + data: { + createOrganization: { + result: { + id: `${toGlobalId('organization', org._key)}`, + acronym: org.acronym, + slug: org.slug, + name: org.name, + verified: org.verified, + externalId: org.externalId, + }, + }, + }, + } + + // externalId is returned as null if not set, not undefined + expectedResponse.data.createOrganization.result.externalId = null + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully created a new organization: ${org.slug}`]) + }) + }) + describe('language is set to french', () => { + it('returns the organizations information', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createOrganization( + input: { + acronymEN: "TBS" + acronymFR: "SCT" + nameEN: "Treasury Board of Canada Secretariat" + nameFR: "Secrétariat du Conseil Trésor du Canada" + externalId: "EXT123" + verified: false + } + ) { + result { + ... on Organization { + id + acronym + slug + name + verified + externalId + } + } + } + } + `, + rootValue: null, + contextValue: { + request: { + language: 'fr', + ip: '1.2.3.4', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + checkSuperAdmin: checkSuperAdmin({ i18n, query, userKey: user._key }), + superAdminRequired: superAdminRequired({ i18n }), + }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'fr', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + validators: { + cleanseInput, + slugify, + }, + }, + }) + + const orgCursor = await query` + FOR org IN organizations + FILTER (LOWER("secretariat-du-conseil-tresor-du-canada") == LOWER(TRANSLATE("fr", org.orgDetails).slug)) + RETURN MERGE({ _id: org._id, _key: org._key, _rev: org._rev, verified: org.verified }, TRANSLATE("fr", org.orgDetails)) + ` + + const org = await orgCursor.next() + + const expectedResponse = { + data: { + createOrganization: { + result: { + id: `${toGlobalId('organization', org._key)}`, + acronym: org.acronym, + slug: org.slug, + name: org.name, + verified: org.verified, + externalId: org.externalId, + }, + }, + }, + } + + // externalId is returned as null if not set, not undefined + expectedResponse.data.createOrganization.result.externalId = null + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully created a new organization: treasury-board-of-canada-secretariat`, + ]) + }) + }) + }) + describe('given an unsuccessful org creation', () => { + describe('users language is set to english', () => { + describe('organization already exists', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createOrganization( + input: { + acronymEN: "TBS" + acronymFR: "SCT" + nameEN: "Treasury Board of Canada Secretariat" + nameFR: "Secrétariat du Conseil Trésor du Canada" + } + ) { + result { + ... on Organization { + id + acronym + slug + name + verified + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + userRequired: jest.fn().mockReturnValue({ + _key: 123, + }), + verifiedRequired: jest.fn(), + checkSuperAdmin: jest.fn(), + superAdminRequired: jest.fn(), + }, + dataSources: { organization: { bySlug: { loadMany: jest.fn().mockReturnValue([{}, undefined]) } } }, + loaders: { + loadUserByKey: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + }, + }) + + const error = { + data: { + createOrganization: { + result: { + code: 400, + description: 'Organization name already in use. Please try again with a different name.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to create an organization that already exists: treasury-board-of-canada-secretariat`, + ]) + }) + }) + describe('data source error occurs', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createOrganization( + input: { + acronymEN: "TBS" + acronymFR: "SCT" + nameEN: "Treasury Board of Canada Secretariat" + nameFR: "Secrétariat du Conseil Trésor du Canada" + } + ) { + result { + ... on Organization { + id + acronym + slug + name + verified + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { language: 'en' }, + userKey: 123, + auth: { + userRequired: jest.fn().mockReturnValue({ _key: 123 }), + verifiedRequired: jest.fn(), + checkSuperAdmin: jest.fn(), + superAdminRequired: jest.fn(), + }, + dataSources: { + organization: { + bySlug: { loadMany: jest.fn().mockReturnValue([undefined, undefined]) }, + create: jest.fn().mockRejectedValue(new Error('Unable to create organization. Please try again.')), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Unable to create organization. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('organization already exists', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createOrganization( + input: { + acronymEN: "TBS" + acronymFR: "SCT" + nameEN: "Treasury Board of Canada Secretariat" + nameFR: "Secrétariat du Conseil Trésor du Canada" + } + ) { + result { + ... on Organization { + id + acronym + slug + name + verified + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + userRequired: jest.fn().mockReturnValue({ + _key: 123, + }), + verifiedRequired: jest.fn(), + checkSuperAdmin: jest.fn(), + superAdminRequired: jest.fn(), + }, + dataSources: { organization: { bySlug: { loadMany: jest.fn().mockReturnValue([{}, undefined]) } } }, + loaders: { + loadUserByKey: jest.fn(), + }, + validators: { + cleanseInput, + slugify, + }, + }, + }) + + const error = { + data: { + createOrganization: { + result: { + code: 400, + description: "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to create an organization that already exists: treasury-board-of-canada-secretariat`, + ]) + }) + }) + describe('data source error occurs', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + createOrganization( + input: { + acronymEN: "TBS" + acronymFR: "SCT" + nameEN: "Treasury Board of Canada Secretariat" + nameFR: "Secrétariat du Conseil Trésor du Canada" + } + ) { + result { + ... on Organization { + id + acronym + slug + name + verified + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request: { language: 'en' }, + userKey: 123, + auth: { + userRequired: jest.fn().mockReturnValue({ _key: 123 }), + verifiedRequired: jest.fn(), + checkSuperAdmin: jest.fn(), + superAdminRequired: jest.fn(), + }, + dataSources: { + organization: { + bySlug: { loadMany: jest.fn().mockReturnValue([undefined, undefined]) }, + create: jest.fn().mockRejectedValue(new Error('Impossible de créer une organisation. Veuillez réessayer.')), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) + + const error = [new GraphQLError('Impossible de créer une organisation. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) + }) + }) + }) + }) +}) diff --git a/api/src/organization/mutations/__tests__/remove-organization.test.js b/api/src/organization/mutations/__tests__/remove-organization.test.js new file mode 100644 index 0000000000..7b179c6d81 --- /dev/null +++ b/api/src/organization/mutations/__tests__/remove-organization.test.js @@ -0,0 +1,3010 @@ +import { setupI18n } from '@lingui/core' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { OrganizationDataSource } from '../../data-source' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('removing an organization', () => { + let query, drop, truncate, schema, collections, transaction, user, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + describe('given a successful removal', () => { + let org, domain + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + }) + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, + }) + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, + }) + + const webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) + + const dmarcSummary = await collections.dmarcSummaries.save({ + dmarcSummary: true, + }) + await collections.domainsToDmarcSummaries.save({ + _from: domain._id, + _to: dmarcSummary._id, + }) + }) + afterEach(async () => { + await truncate() + await drop() + }) + describe('users permission is super admin', () => { + describe('org is verified', () => { + beforeEach(async () => { + org = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + const superAdminOrg = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + }, + }, + }) + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + describe('it owns the dmarc summary data', () => { + beforeEach(async () => { + await collections.ownership.save({ + _from: org._id, + _to: domain._id, + }) + }) + it('removes the dmarc summary data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + + const testDmarcSummaryCursor = + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + const testDmarcSummary = await testDmarcSummaryCursor.next() + expect(testDmarcSummary).toEqual(undefined) + + const testDomainsToDmarcSumCursor = + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() + expect(testDomainsToDmarcSum).toEqual(undefined) + }) + it('removes the ownership edge', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnership = await testOwnershipCursor.next() + expect(testOwnership).toEqual(undefined) + }) + }) + describe('it does not own the dmarc summary data', () => { + it('does not remove the dmarc summary data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + + const testDmarcSummaryCursor = + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + const testDmarcSummary = await testDmarcSummaryCursor.next() + expect(testDmarcSummary).toBeDefined() + + const testDomainsToDmarcSumCursor = + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() + expect(testDomainsToDmarcSum).toBeDefined() + }) + }) + describe('org is the only one claiming the domain', () => { + it('removes the web scan result data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + + const testWebScanCursor = await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(undefined) + }) + it('removes the scan data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) + }) + + it('removes the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck).toEqual(undefined) + }) + it('removes the affiliations, and org', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` + + const testAffiliationCursor = + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + + const testOrgCursor = + await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` + const testOrg = await testOrgCursor.next() + expect(testOrg).toEqual(undefined) + }) + }) + describe('multiple orgs claim the domain', () => { + beforeEach(async () => { + const secondOrg = await collections.organizations.save({}) + await collections.claims.save({ + _from: secondOrg._id, + _to: domain._id, + }) + }) + it('does not remove the web scan result data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(true) + }) + it('does not remove the scan data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(true) + }) + it('does not remove the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck).toBeDefined() + }) + it('removes the affiliations, and org', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` + + const testAffiliationCursor = + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + + const testOrgCursor = + await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` + const testOrg = await testOrgCursor.next() + expect(testOrg).toEqual(undefined) + }) + }) + }) + describe('org is not verified', () => { + beforeEach(async () => { + org = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + const superAdminOrg = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + }, + }, + }) + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + describe('it owns the dmarc summary data', () => { + beforeEach(async () => { + await collections.ownership.save({ + _from: org._id, + _to: domain._id, + }) + }) + it('removes the dmarc summary data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + + const testDmarcSummaryCursor = + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + const testDmarcSummary = await testDmarcSummaryCursor.next() + expect(testDmarcSummary).toEqual(undefined) + + const testDomainsToDmarcSumCursor = + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() + expect(testDomainsToDmarcSum).toEqual(undefined) + }) + it('removes the ownership edge', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnership = await testOwnershipCursor.next() + expect(testOwnership).toEqual(undefined) + }) + }) + describe('it does not own the dmarc summary data', () => { + it('does not remove the dmarc summary data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + + const testDmarcSummaryCursor = + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + const testDmarcSummary = await testDmarcSummaryCursor.next() + expect(testDmarcSummary).toBeDefined() + + const testDomainsToDmarcSumCursor = + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() + expect(testDomainsToDmarcSum).toBeDefined() + }) + }) + describe('org is the only one claiming the domain', () => { + it('removes the web scan result data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testWebScanCursor = await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(undefined) + }) + it('removes the scan data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) + }) + it('removes the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck).toEqual(undefined) + }) + it('removes the affiliations, and org', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` + + const testAffiliationCursor = + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + + const testOrgCursor = + await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` + const testOrg = await testOrgCursor.next() + expect(testOrg).toEqual(undefined) + }) + }) + describe('multiple orgs claim the domain', () => { + beforeEach(async () => { + const secondOrg = await collections.organizations.save({}) + await collections.claims.save({ + _from: secondOrg._id, + _to: domain._id, + }) + }) + it('does not remove the web scan result data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(true) + }) + it('does not remove the scan data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(true) + }) + it('does not remove the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck).toBeDefined() + }) + it('removes the affiliations, and org', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` + + const testAffiliationCursor = + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + + const testOrgCursor = + await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` + const testOrg = await testOrgCursor.next() + expect(testOrg).toEqual(undefined) + }) + }) + }) + }) + describe('users permission is owner', () => { + beforeEach(async () => { + org = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'owner', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + describe('org is not verified', () => { + describe('it owns the dmarc summary data', () => { + beforeEach(async () => { + await collections.ownership.save({ + _from: org._id, + _to: domain._id, + }) + }) + it('removes the dmarc summary data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + + const testDmarcSummaryCursor = + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + const testDmarcSummary = await testDmarcSummaryCursor.next() + expect(testDmarcSummary).toEqual(undefined) + + const testDomainsToDmarcSumCursor = + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() + expect(testDomainsToDmarcSum).toEqual(undefined) + }) + it('removes the ownership edge', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + + const testOwnershipCursor = await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const testOwnership = await testOwnershipCursor.next() + expect(testOwnership).toEqual(undefined) + }) + }) + describe('it does not own the dmarc summary data', () => { + it('does not remove the dmarc summary data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + + const testDmarcSummaryCursor = + await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` + const testDmarcSummary = await testDmarcSummaryCursor.next() + expect(testDmarcSummary).toBeDefined() + + const testDomainsToDmarcSumCursor = + await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() + expect(testDomainsToDmarcSum).toBeDefined() + }) + }) + describe('org is the only one claiming the domain', () => { + it('removes the web scan result data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(undefined) + }) + it('removes the scan data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) + }) + it('removes the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck).toEqual(undefined) + }) + it('removes the affiliations, and org', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` + + const testAffiliationCursor = + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + + const testOrgCursor = + await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` + const testOrg = await testOrgCursor.next() + expect(testOrg).toEqual(undefined) + }) + }) + describe('multiple orgs claim the domain', () => { + beforeEach(async () => { + const secondOrg = await collections.organizations.save({}) + await collections.claims.save({ + _from: secondOrg._id, + _to: domain._id, + }) + }) + it('does not remove the web scan result data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testWebScanCursor = + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` + const testWebScan = await testWebScanCursor.next() + expect(testWebScan).toEqual(true) + }) + it('does not remove the scan data', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) + + const testWebCursor = await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(true) + }) + it('does not remove the domain', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + + const domainCursor = await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const domainCheck = await domainCursor.next() + expect(domainCheck).toBeDefined() + }) + it('removes the affiliations, and org', async () => { + await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` + + const testAffiliationCursor = + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } FILTER aff._from == ${org._key} RETURN aff` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + + const testOrgCursor = + await query`FOR org IN organizations OPTIONS { waitForSync: true } FILTER org._key == ${org._key} RETURN org` + const testOrg = await testOrgCursor.next() + expect(testOrg).toEqual(undefined) + }) + }) + }) + }) + }) + describe('given an unsuccessful removal', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('the requested org is undefined', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue(undefined), + } } }, + }, + }) + + const expectedResponse = { + data: { + removeOrganization: { + result: { + code: 400, + description: 'Unable to remove unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to remove org: 123, but there is no org associated with that id.`, + ]) + }) + }) + describe('given an incorrect permission', () => { + describe('users belong to the org', () => { + describe('users role is owner', () => { + describe('user attempts to remove a verified org', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('owner'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + } } }, + }, + }) + + const expectedResponse = { + data: { + removeOrganization: { + result: { + code: 403, + description: + 'Permission Denied: Please contact super admin for help with removing organization.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to remove org: 123, however the user is not a super admin.`, + ]) + }) + }) + }) + describe('users role is user', () => { + describe('they attempt to remove the org', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('user'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + verified: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + } } }, + }, + }) + + const expectedResponse = { + data: { + removeOrganization: { + result: { + code: 403, + description: + 'Permission Denied: Please contact organization admin for help with removing organization.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to remove org: 123, however the user does not have permission to this organization.`, + ]) + }) + }) + }) + }) + }) + describe('given a data source error', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: 123, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: jest.fn().mockReturnValue('owner'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: { + byKey: { + load: jest.fn().mockReturnValue({ + _id: 'organizations/123', + _key: '123', + verified: false, + slug: 'treasury-board-secretariat', + name: 'Treasury Board of Canada Secretariat', + }), + }, + getRawByKey: jest.fn().mockResolvedValue({ + orgDetails: { + en: { name: 'Treasury Board of Canada Secretariat' }, + fr: { name: 'Secrétariat du Conseil Trésor du Canada' }, + }, + }), + remove: jest.fn().mockRejectedValue(new Error('Unable to remove organization. Please try again.')), + }, + }, + }, + }) + + const error = [new GraphQLError('Unable to remove organization. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) + }) + }) + }) + }) +}) diff --git a/api/src/organization/mutations/__tests__/update-organization.test.js b/api/src/organization/mutations/__tests__/update-organization.test.js new file mode 100644 index 0000000000..1a169220ba --- /dev/null +++ b/api/src/organization/mutations/__tests__/update-organization.test.js @@ -0,0 +1,872 @@ +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput, slugify } from '../../../validators' + +const ORG_KEY = 'org123' +const ORG_GID = toGlobalId('organization', ORG_KEY) + +const BASE_RAW_ORG = { + _id: `organizations/${ORG_KEY}`, + _key: ORG_KEY, + externalId: 'ext-001', + externallyManaged: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, +} + +// Flat object returned by byKey.load after the update (language-resolved, as the Organization type expects). +// _type: 'organization' is required by updateOrganizationUnion resolveType. +const RESOLVED_ORG = { + _type: 'organization', + id: ORG_KEY, + _key: ORG_KEY, + _id: `organizations/${ORG_KEY}`, + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + slug: 'treasury-board-secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + verified: false, +} + +const BASE_USER = { _key: 'user123', userName: 'test@example.com' } + +describe('updateOrganization', () => { + let schema, enI18n, frI18n + const consoleOutput = [] + + beforeAll(() => { + console.info = (o) => consoleOutput.push(o) + console.warn = (o) => consoleOutput.push(o) + console.error = (o) => consoleOutput.push(o) + + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + + enI18n = setupI18n({ + locale: 'en', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + + frI18n = setupI18n({ + locale: 'fr', + localeData: { en: { plurals: {} }, fr: { plurals: {} } }, + locales: ['en', 'fr'], + messages: { en: englishMessages.messages, fr: frenchMessages.messages }, + }) + }) + + afterEach(() => { + consoleOutput.length = 0 + }) + + // Builds the organization data source mock. firstLoad is what byKey.load returns on the + // initial existence check; secondLoad is what it returns after the update + cache clear. + // Note: uses hasOwnProperty so callers can explicitly pass undefined (org not found). + function makeOrgDS(opts = {}) { + const firstLoad = Object.prototype.hasOwnProperty.call(opts, 'firstLoad') ? opts.firstLoad : BASE_RAW_ORG + const secondLoad = opts.secondLoad ?? RESOLVED_ORG + const nameInUseCount = opts.nameInUseCount ?? 0 + const rawOrg = opts.rawOrg ?? BASE_RAW_ORG + const update = opts.update ?? jest.fn() + const checkNameInUse = opts.checkNameInUse ?? null + const getRawByKey = opts.getRawByKey ?? null + + return { + byKey: { + load: jest.fn().mockResolvedValueOnce(firstLoad).mockResolvedValueOnce(secondLoad), + clear: jest.fn(), + }, + checkNameInUse: checkNameInUse ?? jest.fn().mockResolvedValue({ count: nameInUseCount }), + getRawByKey: getRawByKey ?? jest.fn().mockResolvedValue(rawOrg), + update, + } + } + + function makeContext({ + i18n = enI18n, + permission = 'admin', + orgDS = null, + logActivity = jest.fn(), + userKey = 'user123', + ip = '1.2.3.4', + } = {}) { + return { + i18n, + userKey, + request: { ip }, + auth: { + userRequired: jest.fn().mockReturnValue(BASE_USER), + verifiedRequired: jest.fn(), + checkPermission: jest.fn().mockReturnValue(permission), + }, + dataSources: { + auditLogs: { logActivity }, + organization: orgDS ?? makeOrgDS(), + }, + validators: { cleanseInput, slugify }, + } + } + + describe('given a successful update', () => { + it('returns the updated organization on success', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { + id: "${ORG_GID}" + nameEN: "New English Name" + nameFR: "Nouveau Nom Français" + acronymEN: "NEN" + acronymFR: "NNF" + }) { + result { + ... on Organization { id name acronym slug } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext(), + }) + + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toMatchObject({ + name: RESOLVED_ORG.name, + acronym: RESOLVED_ORG.acronym, + slug: RESOLVED_ORG.slug, + }) + expect(consoleOutput).toContain(`User: user123, successfully updated org ${ORG_KEY}.`) + }) + + it('calls update with correctly merged org details', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Updated Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + orgKey: ORG_KEY, + updatedOrgDetails: expect.objectContaining({ + orgDetails: expect.objectContaining({ + en: expect.objectContaining({ name: 'Updated Name' }), + }), + }), + }), + ) + }) + + it('preserves existing fields when only partial inputs are provided', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Only EN Updated" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails.orgDetails.fr.name).toBe(BASE_RAW_ORG.orgDetails.fr.name) + expect(updatedOrgDetails.orgDetails.en.acronym).toBe(BASE_RAW_ORG.orgDetails.en.acronym) + }) + + it('clears byKey cache before reloading the organization', async () => { + const clear = jest.fn() + const orgDS = makeOrgDS() + orgDS.byKey.clear = clear + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Test" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(clear).toHaveBeenCalledWith(ORG_KEY) + }) + }) + + describe('audit logging', () => { + it('logs audit activity when nameEN is changed', async () => { + const logActivity = jest.fn() + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Brand New Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ logActivity }), + }) + + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'update', + target: expect.objectContaining({ + resourceType: 'organization', + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ + name: 'nameEN', + oldValue: BASE_RAW_ORG.orgDetails.en.name, + newValue: 'Brand New Name', + }), + ]), + }), + }), + ) + }) + + it('logs audit activity when nameFR is changed', async () => { + const logActivity = jest.fn() + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameFR: "Nouveau Nom" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ logActivity }), + }) + + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + target: expect.objectContaining({ + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ name: 'nameFR', newValue: 'Nouveau Nom' }), + ]), + }), + }), + ) + }) + + it('logs audit activity when acronymEN is changed', async () => { + const logActivity = jest.fn() + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" acronymEN: "NEW" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ logActivity }), + }) + + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + target: expect.objectContaining({ + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ name: 'acronymEN', newValue: 'NEW' }), + ]), + }), + }), + ) + }) + + it('logs audit activity when acronymFR is changed', async () => { + const logActivity = jest.fn() + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" acronymFR: "NVL" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ logActivity }), + }) + + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + target: expect.objectContaining({ + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ name: 'acronymFR', newValue: 'NVL' }), + ]), + }), + }), + ) + }) + + it('does not log when only zone/sector/location fields are updated', async () => { + const logActivity = jest.fn() + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" zoneEN: "NEWZONE" sectorEN: "NEWSECTOR" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ logActivity }), + }) + + expect(logActivity).not.toHaveBeenCalled() + }) + + it('populates initiatedBy with user key, userName, role, and IP address', async () => { + const logActivity = jest.fn() + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Some Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ logActivity, ip: '10.0.0.1', permission: 'admin' }), + }) + + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + initiatedBy: { + id: BASE_USER._key, + userName: BASE_USER.userName, + role: 'admin', + ipAddress: '10.0.0.1', + }, + }), + ) + }) + }) + + describe('super_admin exclusive fields', () => { + it('super_admin can set externallyManaged to true', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externallyManaged: true }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) + + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + updatedOrgDetails: expect.objectContaining({ externallyManaged: true }), + }), + ) + }) + + it('super_admin can set externallyManaged to false', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externallyManaged: false }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) + + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + updatedOrgDetails: expect.objectContaining({ externallyManaged: false }), + }), + ) + }) + + it('admin cannot set externallyManaged — field is omitted from update payload', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externallyManaged: true }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'admin', orgDS }), + }) + + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails).not.toHaveProperty('externallyManaged') + }) + + it('externallyManaged omitted from input is never set even for super_admin', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "No Managed Field" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) + + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails).not.toHaveProperty('externallyManaged') + }) + + it('super_admin can update externalId', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externalId: "new-ext-id" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) + + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + updatedOrgDetails: expect.objectContaining({ externalId: 'new-ext-id' }), + }), + ) + }) + + it('admin cannot update externalId — field is omitted from update payload', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externalId: "should-be-ignored" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'admin', orgDS }), + }) + + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails).not.toHaveProperty('externalId') + }) + + it('super_admin falls back to existing externalId when none is provided', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Some Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) + + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails.externalId).toBe(BASE_RAW_ORG.externalId) + }) + }) + + describe('error: unknown organization', () => { + it('returns code 400 with correct message (EN) and logs a warning', async () => { + const orgDS = makeOrgDS({ firstLoad: undefined }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Whatever" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toEqual({ + code: 400, + description: 'Unable to update unknown organization.', + }) + expect(consoleOutput).toContain( + `User: user123 attempted to update organization: ${ORG_KEY}, however no organizations is associated with that id.`, + ) + }) + + it('returns a translated 400 error (FR)', async () => { + const orgDS = makeOrgDS({ firstLoad: undefined }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Whatever" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ i18n: frI18n, orgDS }), + }) + + expect(response.data.updateOrganization.result.code).toBe(400) + expect(response.data.updateOrganization.result.description).not.toBe('Unable to update unknown organization.') + }) + }) + + describe('error: insufficient permission', () => { + it('returns code 403 with correct message for user role (EN) and logs an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Whatever" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ permission: 'user' }), + }) + + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toEqual({ + code: 403, + description: 'Permission Denied: Please contact organization admin for help with updating organization.', + }) + expect(consoleOutput).toContain( + `User: user123 attempted to update organization ${ORG_KEY}, however they do not have the correct permission level. Permission: user`, + ) + }) + + it('returns a translated 403 error for user role (FR)', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Whatever" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ i18n: frI18n, permission: 'user' }), + }) + + expect(response.data.updateOrganization.result.code).toBe(403) + expect(response.data.updateOrganization.result.description).not.toBe( + 'Permission Denied: Please contact organization admin for help with updating organization.', + ) + }) + + it('returns code 403 for undefined permission (no org affiliation)', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Whatever" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ permission: null }), + }) + + expect(response.data.updateOrganization.result.code).toBe(403) + }) + }) + + describe('error: organization name already in use', () => { + it('returns code 400 with correct message (EN) and logs an error', async () => { + const orgDS = makeOrgDS({ nameInUseCount: 1 }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Taken Name" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toEqual({ + code: 400, + description: 'Organization name already in use, please choose another and try again.', + }) + expect(consoleOutput).toContain( + `User: user123 attempted to change the name of org: ${ORG_KEY} however it is already in use.`, + ) + }) + + it('returns a translated 400 error when name conflicts (FR)', async () => { + const orgDS = makeOrgDS({ nameInUseCount: 1 }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Taken Name" }) { + result { + ... on OrganizationError { code description } + ... on Organization { id } + } + } + } + `, + contextValue: makeContext({ i18n: frI18n, orgDS }), + }) + + expect(response.data.updateOrganization.result.code).toBe(400) + expect(response.data.updateOrganization.result.description).not.toBe( + 'Organization name already in use, please choose another and try again.', + ) + }) + + it('skips the name check when neither nameEN nor nameFR is provided', async () => { + const checkNameInUse = jest.fn() + const orgDS = makeOrgDS({ checkNameInUse }) + + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" zoneEN: "NewZone" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(checkNameInUse).not.toHaveBeenCalled() + }) + }) + + describe('error: data source failures', () => { + it('propagates error thrown by organizationDS.update', async () => { + const orgDS = makeOrgDS({ + update: jest.fn().mockRejectedValue(new Error('Unable to load organization. Please try again.')), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Fail Update" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(response.errors).toBeDefined() + expect(response.errors[0].message).toBe('Unable to load organization. Please try again.') + }) + + it('propagates error thrown by organizationDS.checkNameInUse', async () => { + const orgDS = makeOrgDS({ + checkNameInUse: jest.fn().mockRejectedValue(new Error('Unable to update organization. Please try again.')), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Fail Check" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(response.errors).toBeDefined() + expect(response.errors[0].message).toBe('Unable to update organization. Please try again.') + }) + + it('propagates error thrown by organizationDS.getRawByKey', async () => { + const orgDS = makeOrgDS({ + getRawByKey: jest.fn().mockRejectedValue(new Error('Unable to load organization. Please try again.')), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" zoneEN: "FED" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } + } + } + } + `, + contextValue: makeContext({ orgDS }), + }) + + expect(response.errors).toBeDefined() + expect(response.errors[0].message).toBe('Unable to load organization. Please try again.') + }) + }) +}) diff --git a/api/src/organization/mutations/__tests__/verify-organization.test.js b/api/src/organization/mutations/__tests__/verify-organization.test.js new file mode 100644 index 0000000000..86a37edfa5 --- /dev/null +++ b/api/src/organization/mutations/__tests__/verify-organization.test.js @@ -0,0 +1,985 @@ +import { setupI18n } from '@lingui/core' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { loadDomainByKey } from '../../../domain/loaders' +import { OrganizationDataSource } from '../../data-source' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('removing an organization', () => { + let query, drop, truncate, schema, collections, transaction, i18n, user + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful org verification', () => { + let org, domain + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + org = await collections.organizations.save({ + verified: false, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'super_admin', + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + archived: true, + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('super admin is able to verify organization', () => { + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + i18n, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + i18n, + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + }, + }) + + const expectedResponse = { + data: { + verifyOrganization: { + result: { + status: 'Successfully verified organization: treasury-board-secretariat.', + organization: { + name: 'Treasury Board of Canada Secretariat', + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key}, successfully verified org: ${org._key}.`]) + + const orgDS = new OrganizationDataSource({ query, userKey: user._key, i18n, language: 'en', cleanseInput, transaction, collections: collectionNames }) + const verifiedOrg = await orgDS.byKey.load(org._key) + expect(verifiedOrg.verified).toEqual(true) + + const domainLoader = loadDomainByKey({ query, userKey: user._key, i18n }) + + const unarchivedDomain = await domainLoader.load(domain._key) + expect(unarchivedDomain.archived).toEqual(false) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('super admin is able to verify organization', () => { + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', org._key)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, + i18n, + }), + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + i18n, + }), + verifiedRequired: verifiedRequired({}), + }, + validators: { cleanseInput }, + dataSources: { + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'fr', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + }, + }) + + const expectedResponse = { + data: { + verifyOrganization: { + result: { + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + organization: { + name: 'Secrétariat du Conseil Trésor du Canada', + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key}, successfully verified org: ${org._key}.`]) + + const orgDS = new OrganizationDataSource({ query, userKey: user._key, i18n, language: 'fr', cleanseInput, transaction, collections: collectionNames }) + const verifiedOrg = await orgDS.byKey.load(org._key) + expect(verifiedOrg.verified).toEqual(true) + }) + }) + }) + }) + describe('given an unsuccessful org verification', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('organization is not found', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', -1)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue(undefined), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 400, + description: 'Unable to verify unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: -1, however no organizations is associated with that id.`, + ]) + }) + }) + describe('user permission is not super admin', () => { + describe('users permission level is admin', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _id: 'organizations/123', + }), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 403, + description: + 'Permission Denied: Please contact super admin for help with verifying this organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: admin`, + ]) + }) + }) + describe('users permission level is user', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('user'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _id: 'organizations/123', + }), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 403, + description: + 'Permission Denied: Please contact super admin for help with verifying this organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: user`, + ]) + }) + }) + }) + describe('organization is already verified', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('super_admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + verified: true, + }), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 400, + description: 'Organization has already been verified.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: 123, however the organization has already been verified.`, + ]) + }) + }) + describe('data source error occurs', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('super_admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { + organization: { + byKey: { load: jest.fn().mockReturnValue({ verified: false, _key: 123 }) }, + verify: jest.fn().mockRejectedValue(new Error('Unable to verify organization. Please try again.')), + }, + }, + }, + }) + + const error = [new GraphQLError('Unable to verify organization. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('organization is not found', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', -1)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn(), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue(undefined), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 400, + description: 'Impossible de vérifier une organisation inconnue.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: -1, however no organizations is associated with that id.`, + ]) + }) + }) + describe('user permission is not super admin', () => { + describe('users permission level is admin', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _id: 'organizations/123', + }), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 403, + description: + "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: admin`, + ]) + }) + }) + describe('users permission level is user', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('user'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + _id: 'organizations/123', + }), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 403, + description: + "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: 123, however they do not have the correct permission level. Permission: user`, + ]) + }) + }) + }) + describe('organization is already verified', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('super_admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { organization: { byKey: { + load: jest.fn().mockReturnValue({ + verified: true, + }), + } } }, + }, + }) + + const error = { + data: { + verifyOrganization: { + result: { + code: 400, + description: "L'organisation a déjà été vérifiée.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to verify organization: 123, however the organization has already been verified.`, + ]) + }) + }) + describe('data source error occurs', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" + } + ) { + result { + ... on OrganizationResult { + status + organization { + name + } + } + ... on OrganizationError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: 123, + auth: { + checkPermission: jest.fn().mockReturnValue('super_admin'), + userRequired: jest.fn(), + verifiedRequired: jest.fn(), + }, + validators: { cleanseInput }, + dataSources: { + organization: { + byKey: { load: jest.fn().mockReturnValue({ verified: false, _key: 123 }) }, + verify: jest.fn().mockRejectedValue(new Error("Impossible de vérifier l'organisation. Veuillez réessayer.")), + }, + }, + }, + }) + + const error = [new GraphQLError("Impossible de vérifier l'organisation. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) + }) + }) + }) + }) +}) diff --git a/api/src/organization/mutations/archive-organization.js b/api/src/organization/mutations/archive-organization.js new file mode 100644 index 0000000000..583fa0de54 --- /dev/null +++ b/api/src/organization/mutations/archive-organization.js @@ -0,0 +1,104 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { removeOrganizationUnion } from '../unions' +import ac from '../../access-control' + +export const archiveOrganization = new mutationWithClientMutationId({ + name: 'ArchiveOrganization', + description: 'This mutation allows the archival of unused organizations.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish you archive.', + }, + }), + outputFields: () => ({ + result: { + type: new GraphQLNonNull(removeOrganizationUnion), + description: '`RemoveOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired }, + validators: { cleanseInput }, + dataSources: { auditLogs, organization: organizationDS }, + }, + ) => { + // Get user + const user = await userRequired() + + verifiedRequired({ user }) + + // Cleanse Input + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + // Get org from db + const organization = await organizationDS.byKey.load(orgId) + + // Check to see if org exists + if (!organization) { + console.warn(`User: ${userKey} attempted to archive org: ${orgId}, but there is no org associated with that id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to archive unknown organization.`), + } + } + + // Get users permission + const permission = await checkPermission({ orgId: organization._id }) + + if (!ac.can(permission).deleteAny('organization').granted) { + console.warn( + `User: ${userKey} attempted to archive org: ${organization._key}, however they do not have the correct permission level. Permission: ${permission}`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with archiving organization.`), + } + } + + await organizationDS.archive({ organization }) + + console.info(`User: ${userKey} successfully archived org: ${organization._key}.`) + + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + updatedProperties: [ + { + name: 'archived', + oldValue: false, + newValue: true, + }, + ], + target: { + resource: { + en: organization.name, + fr: organization.name, + }, + resourceType: 'organization', + }, + }) + + return { + _type: 'result', + status: i18n._(t`Successfully archived organization: ${organization.slug}.`), + organization, + } + }, +}) diff --git a/api/src/organization/mutations/create-organization.js b/api/src/organization/mutations/create-organization.js new file mode 100644 index 0000000000..10c2d638e4 --- /dev/null +++ b/api/src/organization/mutations/create-organization.js @@ -0,0 +1,132 @@ +import { GraphQLNonNull, GraphQLString, GraphQLBoolean } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { Acronym } from '../../scalars' +import { createOrganizationUnion } from '../unions' + +export const createOrganization = new mutationWithClientMutationId({ + name: 'CreateOrganization', + description: 'This mutation allows the creation of an organization inside the database.', + inputFields: () => ({ + acronymEN: { + type: new GraphQLNonNull(Acronym), + description: 'The English acronym of the organization.', + }, + acronymFR: { + type: new GraphQLNonNull(Acronym), + description: 'The French acronym of the organization.', + }, + nameEN: { + type: new GraphQLNonNull(GraphQLString), + description: 'The English name of the organization.', + }, + nameFR: { + type: new GraphQLNonNull(GraphQLString), + description: 'The French name of the organization.', + }, + externalId: { + type: GraphQLString, + description: 'String ID used to identify the organization in an external system.', + }, + verified: { + type: GraphQLBoolean, + description: 'If the organization is verified.', + }, + }), + outputFields: () => ({ + result: { + type: createOrganizationUnion, + description: '`CreateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + request, + userKey, + request: { ip }, + auth: { userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired }, + dataSources: { auditLogs, organization: organizationDS }, + validators: { cleanseInput, slugify }, + }, + ) => { + // Get user + const user = await userRequired() + + verifiedRequired({ user }) + const isSuperAdmin = await checkSuperAdmin() + + if (args.verified === true) { + superAdminRequired({ user, isSuperAdmin }) + } + + // Cleanse Input + const acronymEN = cleanseInput(args.acronymEN) + const acronymFR = cleanseInput(args.acronymFR) + const nameEN = cleanseInput(args.nameEN) + const nameFR = cleanseInput(args.nameFR) + const externalId = cleanseInput(args.externalId) + + // Create EN and FR slugs + const slugEN = slugify(nameEN) + const slugFR = slugify(nameFR) + + // Check to see if org already exists + const [orgEN, orgFR] = await organizationDS.bySlug.loadMany([slugEN, slugFR]) + + if (typeof orgEN !== 'undefined' || typeof orgFR !== 'undefined') { + console.warn(`User: ${userKey} attempted to create an organization that already exists: ${slugEN}`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Organization name already in use. Please try again with a different name.`), + } + } + + const organizationDetails = { + verified: args.verified || false, + externallyManaged: false, + externalId, + orgDetails: { + en: { + slug: slugEN, + acronym: acronymEN, + name: nameEN, + }, + fr: { + slug: slugFR, + acronym: acronymFR, + name: nameFR, + }, + }, + } + + const organization = await organizationDS.create({ + organizationDetails, + userId: user._id, + language: request.language, + }) + + console.info(`User: ${userKey} successfully created a new organization: ${slugEN}`) + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + ipAddress: ip, + }, + action: 'create', + target: { + resource: { + en: organizationDetails.orgDetails.en.name, + fr: organizationDetails.orgDetails.fr.name, + }, + resourceType: 'organization', + }, + }) + + return organization + }, +}) diff --git a/api/src/organization/mutations/index.js b/api/src/organization/mutations/index.js new file mode 100644 index 0000000000..2ee2d34e15 --- /dev/null +++ b/api/src/organization/mutations/index.js @@ -0,0 +1,5 @@ +export * from './archive-organization' +export * from './create-organization' +export * from './remove-organization' +export * from './update-organization' +export * from './verify-organization' diff --git a/api/src/organization/mutations/remove-organization.js b/api/src/organization/mutations/remove-organization.js new file mode 100644 index 0000000000..114566b58b --- /dev/null +++ b/api/src/organization/mutations/remove-organization.js @@ -0,0 +1,111 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { removeOrganizationUnion } from '../unions' +import ac from '../../access-control' + +export const removeOrganization = new mutationWithClientMutationId({ + name: 'RemoveOrganization', + description: 'This mutation allows the removal of unused organizations.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish you remove.', + }, + }), + outputFields: () => ({ + result: { + type: new GraphQLNonNull(removeOrganizationUnion), + description: '`RemoveOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired }, + validators: { cleanseInput }, + dataSources: { auditLogs, organization: organizationDS }, + }, + ) => { + // Get user + const user = await userRequired() + + verifiedRequired({ user }) + + // Cleanse Input + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + // Get org from db + const organization = await organizationDS.byKey.load(orgId) + + // Check to see if org exists + if (!organization) { + console.warn(`User: ${userKey} attempted to remove org: ${orgId}, but there is no org associated with that id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to remove unknown organization.`), + } + } + + // Get users permission + const permission = await checkPermission({ orgId: organization._id }) + + if (!ac.can(permission).deleteOwn('organization').granted) { + console.warn( + `User: ${userKey} attempted to remove org: ${organization._key}, however the user does not have permission to this organization.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._( + t`Permission Denied: Please contact organization admin for help with removing organization.`, + ), + } + } + + // Check to see if org is verified check, and the user is super admin + if (organization.verified && !ac.can(permission).deleteAny('organization').granted) { + console.warn( + `User: ${userKey} attempted to remove org: ${organization._key}, however the user is not a super admin.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with removing organization.`), + } + } + + const compareOrg = await organizationDS.getRawByKey({ orgKey: organization._key }) + await organizationDS.remove({ organization }) + + console.info(`User: ${userKey} successfully removed org: ${organization._key}.`) + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'delete', + target: { + resource: { + en: compareOrg?.orgDetails.en.name || organization.name, + fr: compareOrg?.orgDetails.fr.name || organization.name, + }, + resourceType: 'organization', + }, + }) + + return { + _type: 'result', + status: i18n._(t`Successfully removed organization: ${organization.slug}.`), + organization, + } + }, +}) diff --git a/api/src/organization/mutations/update-organization.js b/api/src/organization/mutations/update-organization.js new file mode 100644 index 0000000000..8e6938a2e7 --- /dev/null +++ b/api/src/organization/mutations/update-organization.js @@ -0,0 +1,265 @@ +import { GraphQLString, GraphQLNonNull, GraphQLID, GraphQLBoolean } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { Acronym } from '../../scalars' +import { updateOrganizationUnion } from '../unions' +import ac from '../../access-control' + +export const updateOrganization = new mutationWithClientMutationId({ + name: 'UpdateOrganization', + description: 'Mutation allows the modification of organizations if any changes to the organization may occur.', + inputFields: () => ({ + id: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization to be updated.', + }, + acronymEN: { + type: Acronym, + description: 'The English acronym of the organization.', + }, + acronymFR: { + type: Acronym, + description: 'The French acronym of the organization.', + }, + nameEN: { + type: GraphQLString, + description: 'The English name of the organization.', + }, + nameFR: { + type: GraphQLString, + description: 'The French name of the organization.', + }, + zoneEN: { + type: GraphQLString, + description: 'The English translation of the zone the organization belongs to.', + }, + zoneFR: { + type: GraphQLString, + description: 'The English translation of the zone the organization belongs to.', + }, + sectorEN: { + type: GraphQLString, + description: 'The English translation of the sector the organization belongs to.', + }, + sectorFR: { + type: GraphQLString, + description: 'The French translation of the sector the organization belongs to.', + }, + countryEN: { + type: GraphQLString, + description: 'The English translation of the country the organization resides in.', + }, + countryFR: { + type: GraphQLString, + description: 'The French translation of the country the organization resides in.', + }, + provinceEN: { + type: GraphQLString, + description: 'The English translation of the province the organization resides in.', + }, + provinceFR: { + type: GraphQLString, + description: 'The French translation of the province the organization resides in.', + }, + cityEN: { + type: GraphQLString, + description: 'The English translation of the city the organization resides in.', + }, + cityFR: { + type: GraphQLString, + description: 'The French translation of the city the organization resides in.', + }, + externallyManaged: { + type: GraphQLBoolean, + description: 'If the organization has domains that are managed externally.', + }, + externalId: { + type: GraphQLString, + description: 'String ID used to identify the organization in an external system.', + }, + }), + outputFields: () => ({ + result: { + type: new GraphQLNonNull(updateOrganizationUnion), + description: '`UpdateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + userKey, + request: { ip }, + auth: { checkPermission, userRequired, verifiedRequired }, + dataSources: { auditLogs, organization: organizationDS }, + validators: { cleanseInput, slugify }, + }, + ) => { + // Get user + const user = await userRequired() + + verifiedRequired({ user }) + + // Cleanse Input + const { type: _orgType, id: orgKey } = fromGlobalId(cleanseInput(args.id)) + const acronymEN = cleanseInput(args.acronymEN) + const acronymFR = cleanseInput(args.acronymFR) + const nameEN = cleanseInput(args.nameEN) + const nameFR = cleanseInput(args.nameFR) + const zoneEN = cleanseInput(args.zoneEN) + const zoneFR = cleanseInput(args.zoneFR) + const sectorEN = cleanseInput(args.sectorEN) + const sectorFR = cleanseInput(args.sectorFR) + const countryEN = cleanseInput(args.countryEN) + const countryFR = cleanseInput(args.countryFR) + const provinceEN = cleanseInput(args.provinceEN) + const provinceFR = cleanseInput(args.provinceFR) + const cityEN = cleanseInput(args.cityEN) + const cityFR = cleanseInput(args.cityFR) + const externalId = cleanseInput(args.externalId) + + // Create Slug + const slugEN = slugify(nameEN) + const slugFR = slugify(nameFR) + + // Check to see if org exists + const currentOrg = await organizationDS.byKey.load(orgKey) + + if (typeof currentOrg === 'undefined') { + console.warn( + `User: ${userKey} attempted to update organization: ${orgKey}, however no organizations is associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update unknown organization.`), + } + } + + // Check to see if user has permission + const permission = await checkPermission({ orgId: currentOrg._id }) + + if (!ac.can(permission).updateOwn('organization').granted) { + console.error( + `User: ${userKey} attempted to update organization ${orgKey}, however they do not have the correct permission level. Permission: ${permission}`, + ) + return { + _type: 'error', + code: 403, + description: i18n._( + t`Permission Denied: Please contact organization admin for help with updating organization.`, + ), + } + } + + // Check to see if any orgs already have the name in use + if (nameEN !== '' || nameFR !== '') { + const orgNameCheckCursor = await organizationDS.checkNameInUse({ nameEN, nameFR }) + if (orgNameCheckCursor.count > 0) { + console.error( + `User: ${userKey} attempted to change the name of org: ${currentOrg._key} however it is already in use.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Organization name already in use, please choose another and try again.`), + } + } + } + + const compareOrg = await organizationDS.getRawByKey({ orgKey }) + + const updatedOrgDetails = { + orgDetails: { + en: { + slug: slugEN || compareOrg.orgDetails.en.slug, + acronym: acronymEN || compareOrg.orgDetails.en.acronym, + name: nameEN || compareOrg.orgDetails.en.name, + zone: zoneEN || compareOrg.orgDetails.en.zone, + sector: sectorEN || compareOrg.orgDetails.en.sector, + country: countryEN || compareOrg.orgDetails.en.country, + province: provinceEN || compareOrg.orgDetails.en.province, + city: cityEN || compareOrg.orgDetails.en.city, + }, + fr: { + slug: slugFR || compareOrg.orgDetails.fr.slug, + acronym: acronymFR || compareOrg.orgDetails.fr.acronym, + name: nameFR || compareOrg.orgDetails.fr.name, + zone: zoneFR || compareOrg.orgDetails.fr.zone, + sector: sectorFR || compareOrg.orgDetails.fr.sector, + country: countryFR || compareOrg.orgDetails.fr.country, + province: provinceFR || compareOrg.orgDetails.fr.province, + city: cityFR || compareOrg.orgDetails.fr.city, + }, + }, + } + + if (ac.can(permission).updateAny('organization').granted && typeof args.externallyManaged !== 'undefined') { + updatedOrgDetails.externallyManaged = args.externallyManaged + } + + if (ac.can(permission).updateAny('organization').granted) { + updatedOrgDetails.externalId = externalId || compareOrg?.externalId + } + + await organizationDS.update({ orgKey, updatedOrgDetails }) + + await organizationDS.byKey.clear(orgKey) + const organization = await organizationDS.byKey.load(orgKey) + + console.info(`User: ${userKey}, successfully updated org ${orgKey}.`) + + const updatedProperties = [] + if (nameEN) { + updatedProperties.push({ + name: 'nameEN', + oldValue: compareOrg.orgDetails.en.name, + newValue: nameEN, + }) + } + if (nameFR) { + updatedProperties.push({ + name: 'nameFR', + oldValue: compareOrg.orgDetails.fr.name, + newValue: nameFR, + }) + } + if (acronymEN) { + updatedProperties.push({ + name: 'acronymEN', + oldValue: compareOrg.orgDetails.en.acronym, + newValue: acronymEN, + }) + } + if (acronymFR) { + updatedProperties.push({ + name: 'acronymFR', + oldValue: compareOrg.orgDetails.fr.acronym, + newValue: acronymFR, + }) + } + if (updatedProperties.length > 0) { + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'update', + target: { + resource: { + en: compareOrg.orgDetails.en.name, + fr: compareOrg.orgDetails.fr.name, + }, + resourceType: 'organization', + updatedProperties, + }, + }) + } + + return organization + }, +}) diff --git a/api/src/organization/mutations/verify-organization.js b/api/src/organization/mutations/verify-organization.js new file mode 100644 index 0000000000..dbb2bb2530 --- /dev/null +++ b/api/src/organization/mutations/verify-organization.js @@ -0,0 +1,94 @@ +import { t } from '@lingui/macro' +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' + +import { verifyOrganizationUnion } from '../unions' + +export const verifyOrganization = new mutationWithClientMutationId({ + name: 'VerifyOrganization', + description: 'Mutation allows the verification of an organization.', + inputFields: () => ({ + orgId: { + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization to be verified.', + }, + }), + outputFields: () => ({ + result: { + type: verifyOrganizationUnion, + description: '`VerifyOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + userKey, + auth: { checkPermission, userRequired, verifiedRequired }, + dataSources: { organization: organizationDS }, + validators: { cleanseInput }, + }, + ) => { + // Ensure that user is required + const user = await userRequired() + + verifiedRequired({ user }) + + const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) + + // Check to see if org exists + const currentOrg = await organizationDS.byKey.load(orgKey) + + if (typeof currentOrg === 'undefined') { + console.warn( + `User: ${userKey} attempted to verify organization: ${orgKey}, however no organizations is associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to verify unknown organization.`), + } + } + + // Check to see if use has permission + const permission = await checkPermission({ orgId: currentOrg._id }) + + if (permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to verify organization: ${orgKey}, however they do not have the correct permission level. Permission: ${permission}`, + ) + return { + _type: 'error', + code: 403, + description: i18n._( + t`Permission Denied: Please contact super admin for help with verifying this organization.`, + ), + } + } + + // Check to see if org is already verified + if (currentOrg.verified) { + console.warn( + `User: ${userKey} attempted to verify organization: ${orgKey}, however the organization has already been verified.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Organization has already been verified.`), + } + } + + currentOrg.verified = true + + await organizationDS.verify({ currentOrg }) + + console.info(`User: ${userKey}, successfully verified org: ${orgKey}.`) + + return { + _type: 'result', + status: i18n._(t`Successfully verified organization: ${currentOrg.slug}.`), + organization: currentOrg, + } + }, +}) diff --git a/api-js/src/organization/objects/__tests__/organization-connection.test.js b/api/src/organization/objects/__tests__/organization-connection.test.js similarity index 100% rename from api-js/src/organization/objects/__tests__/organization-connection.test.js rename to api/src/organization/objects/__tests__/organization-connection.test.js diff --git a/api-js/src/organization/objects/__tests__/organization-error.test.js b/api/src/organization/objects/__tests__/organization-error.test.js similarity index 100% rename from api-js/src/organization/objects/__tests__/organization-error.test.js rename to api/src/organization/objects/__tests__/organization-error.test.js diff --git a/api-js/src/organization/objects/__tests__/organization-result.test.js b/api/src/organization/objects/__tests__/organization-result.test.js similarity index 100% rename from api-js/src/organization/objects/__tests__/organization-result.test.js rename to api/src/organization/objects/__tests__/organization-result.test.js diff --git a/api/src/organization/objects/__tests__/organization-summary.test.js b/api/src/organization/objects/__tests__/organization-summary.test.js new file mode 100644 index 0000000000..ff202a755d --- /dev/null +++ b/api/src/organization/objects/__tests__/organization-summary.test.js @@ -0,0 +1,423 @@ +import { organizationSummaryType } from '../organization-summary' +import { categorizedSummaryType } from '../../../summaries/objects' + +describe('given the organization summary object', () => { + describe('testing field definitions', () => { + it('has a dmarc field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('dmarc') + expect(demoType.dmarc.type).toMatchObject(categorizedSummaryType) + }) + it('has a https field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('https') + expect(demoType.https.type).toMatchObject(categorizedSummaryType) + }) + it('has a mail field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('mail') + expect(demoType.mail.type).toMatchObject(categorizedSummaryType) + }) + it('has a web field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('web') + expect(demoType.web.type).toMatchObject(categorizedSummaryType) + }) + it('has a dmarcPhase field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('dmarcPhase') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a webConnections field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('webConnections') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a ssl field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('ssl') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a spf field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('spf') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a dkim field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('dkim') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + }) + + describe('field resolvers', () => { + describe('dmarc resolver', () => { + describe('total is zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const dmarc = { + pass: 0, + fail: 0, + total: 0, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ + categories: [ + { + count: 0, + name: 'pass', + percentage: 0, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + total: 0, + }) + }) + }) + + describe('when total is greater then zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const dmarc = { + pass: 50, + fail: 1000, + total: 1050, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ + categories: [ + { + count: 50, + name: 'pass', + percentage: 4.8, + }, + { + count: 1000, + name: 'fail', + percentage: 95.2, + }, + ], + total: 1050, + }) + }) + }) + }) + + describe('https resolver', () => { + describe('total is zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const https = { + pass: 0, + fail: 0, + total: 0, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ + categories: [ + { + count: 0, + name: 'pass', + percentage: 0, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + total: 0, + }) + }) + }) + + describe('when total is greater then zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const https = { + pass: 50, + fail: 1000, + total: 1050, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ + categories: [ + { + count: 50, + name: 'pass', + percentage: 4.8, + }, + { + count: 1000, + name: 'fail', + percentage: 95.2, + }, + ], + total: 1050, + }) + }) + }) + }) + describe('mail resolver', () => { + describe('total is zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const mail = { + pass: 0, + fail: 0, + total: 0, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ + categories: [ + { + count: 0, + name: 'pass', + percentage: 0, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + total: 0, + }) + }) + }) + + describe('when total is greater then zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const mail = { + pass: 50, + fail: 1000, + total: 1050, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ + categories: [ + { + count: 50, + name: 'pass', + percentage: 4.8, + }, + { + count: 1000, + name: 'fail', + percentage: 95.2, + }, + ], + total: 1050, + }) + }) + }) + }) + describe('testing web resolver', () => { + describe('total is zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const web = { + pass: 0, + fail: 0, + total: 0, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ + categories: [ + { + count: 0, + name: 'pass', + percentage: 0, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + total: 0, + }) + }) + }) + describe('total is greater then zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const web = { + pass: 50, + fail: 1000, + total: 1050, + } + + const i18n = { + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), + } + + expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ + categories: [ + { + count: 50, + name: 'pass', + percentage: 4.8, + }, + { + count: 1000, + name: 'fail', + percentage: 95.2, + }, + ], + total: 1050, + }) + }) + }) + }) + describe('testing dmarcPhase resolver', () => { + describe('total is zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const dmarcPhase = { + assess: 0, + deploy: 0, + enforce: 0, + maintain: 0, + total: 0, + } + + const i18n = { + _: jest + .fn() + .mockReturnValueOnce('assess') + .mockReturnValueOnce('deploy') + .mockReturnValueOnce('enforce') + .mockReturnValueOnce('maintain'), + } + + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ + categories: [ + { + count: 0, + name: 'assess', + percentage: 0, + }, + { + count: 0, + name: 'deploy', + percentage: 0, + }, + { + count: 0, + name: 'enforce', + percentage: 0, + }, + { + count: 0, + name: 'maintain', + percentage: 0, + }, + ], + total: 0, + }) + }) + }) + + describe('when total is greater then zero', () => { + it('returns the resolved value', () => { + const demoType = organizationSummaryType.getFields() + + const dmarcPhase = { + assess: 75, + deploy: 100, + enforce: 125, + maintain: 200, + total: 500, + } + + const i18n = { + _: jest + .fn() + .mockReturnValueOnce('assess') + .mockReturnValueOnce('deploy') + .mockReturnValueOnce('enforce') + .mockReturnValueOnce('maintain'), + } + + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ + categories: [ + { + count: 75, + name: 'assess', + percentage: 15.0, + }, + { + count: 100, + name: 'deploy', + percentage: 20.0, + }, + { + count: 125, + name: 'enforce', + percentage: 25.0, + }, + { + count: 200, + name: 'maintain', + percentage: 40.0, + }, + ], + total: 500, + }) + }) + }) + }) + }) +}) diff --git a/api-js/src/organization/objects/__tests__/organization.test.js b/api/src/organization/objects/__tests__/organization.test.js similarity index 87% rename from api-js/src/organization/objects/__tests__/organization.test.js rename to api/src/organization/objects/__tests__/organization.test.js index fa71077a3c..b88914a82f 100644 --- a/api-js/src/organization/objects/__tests__/organization.test.js +++ b/api/src/organization/objects/__tests__/organization.test.js @@ -1,10 +1,4 @@ -import { - GraphQLNonNull, - GraphQLID, - GraphQLString, - GraphQLBoolean, - GraphQLInt, -} from 'graphql' +import { GraphQLNonNull, GraphQLID, GraphQLString, GraphQLBoolean, GraphQLInt } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -22,7 +16,7 @@ describe('given the organization object', () => { const demoType = organizationType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has an acronym field', () => { const demoType = organizationType.getFields() @@ -94,17 +88,13 @@ describe('given the organization object', () => { const demoType = organizationType.getFields() expect(demoType).toHaveProperty('domains') - expect(demoType.domains.type).toMatchObject( - domainConnection.connectionType, - ) + expect(demoType.domains.type).toMatchObject(domainConnection.connectionType) }) it('has an affiliations field', () => { const demoType = organizationType.getFields() expect(demoType).toHaveProperty('affiliations') - expect(demoType.affiliations.type).toMatchObject( - affiliationConnection.connectionType, - ) + expect(demoType.affiliations.type).toMatchObject(affiliationConnection.connectionType) }) }) describe('testing the field resolvers', () => { @@ -112,9 +102,7 @@ describe('given the organization object', () => { it('returns the resolved value', () => { const demoType = organizationType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('organization', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('organization', 1)) }) }) describe('testing the acronym resolver', () => { @@ -135,9 +123,7 @@ describe('given the organization object', () => { it('returns the resolved value', () => { const demoType = organizationType.getFields() - expect(demoType.slug.resolve({ slug: 'organization-name' })).toEqual( - 'organization-name', - ) + expect(demoType.slug.resolve({ slug: 'organization-name' })).toEqual('organization-name') }) }) describe('testing the zone resolver', () => { @@ -158,18 +144,14 @@ describe('given the organization object', () => { it('returns the resolved value', () => { const demoType = organizationType.getFields() - expect(demoType.country.resolve({ country: 'Canada' })).toEqual( - 'Canada', - ) + expect(demoType.country.resolve({ country: 'Canada' })).toEqual('Canada') }) }) describe('testing the province resolver', () => { it('returns the resolved value', () => { const demoType = organizationType.getFields() - expect(demoType.province.resolve({ province: 'province' })).toEqual( - 'province', - ) + expect(demoType.province.resolve({ province: 'province' })).toEqual('province') }) }) describe('testing the city resolver', () => { @@ -260,10 +242,11 @@ describe('given the organization object', () => { { _id: 'organizations/1' }, { first: 1 }, { + dataSources: { + auth: { permissionByOrgId: { load: jest.fn().mockResolvedValue('user') } }, + }, loaders: { - loadDomainConnectionsByOrgId: jest - .fn() - .mockReturnValue(expectedResult), + loadDomainConnectionsByOrgId: jest.fn().mockReturnValue(expectedResult), }, }, ), @@ -275,8 +258,6 @@ describe('given the organization object', () => { it('returns the resolved value', async () => { const demoType = organizationType.getFields() - const checkPermission = jest.fn().mockReturnValue('admin') - const expectedResults = { edges: [ { @@ -309,11 +290,13 @@ describe('given the organization object', () => { { _id: 'organizations/1' }, { first: 5 }, { - auth: { checkPermission }, + i18n, + auth: { loginRequiredBool: true }, + dataSources: { + auth: { permissionByOrgId: { load: jest.fn().mockResolvedValue('admin') } }, + }, loaders: { - loadAffiliationConnectionsByOrgId: jest - .fn() - .mockReturnValue(expectedResults), + loadAffiliationConnectionsByOrgId: jest.fn().mockReturnValue(expectedResults), }, }, ), @@ -339,23 +322,22 @@ describe('given the organization object', () => { it('returns the resolved value', async () => { const demoType = organizationType.getFields() - const checkPermission = jest.fn().mockReturnValue('user') - try { await demoType.affiliations.resolve( { _id: '1' }, { first: 5 }, { i18n, - auth: { checkPermission }, + auth: { loginRequiredBool: true }, + dataSources: { + auth: { permissionByOrgId: { load: jest.fn().mockResolvedValue('user') } }, + }, loaders: { loadAffiliationConnectionsByOrgId: jest.fn() }, }, ) } catch (err) { expect(err).toEqual( - new Error( - 'Cannot query affiliations on organization without admin permission or higher.', - ), + new Error('Cannot query affiliations on organization without admin permission or higher.'), ) } }) @@ -378,15 +360,16 @@ describe('given the organization object', () => { it('returns the resolved value', async () => { const demoType = organizationType.getFields() - const checkPermission = jest.fn().mockReturnValue('user') - try { await demoType.affiliations.resolve( { _id: '1' }, { first: 5 }, { i18n, - auth: { checkPermission }, + auth: { loginRequiredBool: true }, + dataSources: { + auth: { permissionByOrgId: { load: jest.fn().mockResolvedValue('user') } }, + }, loaders: { loadAffiliationConnectionsByOrgId: jest.fn() }, }, ) diff --git a/api-js/src/organization/objects/index.js b/api/src/organization/objects/index.js similarity index 100% rename from api-js/src/organization/objects/index.js rename to api/src/organization/objects/index.js diff --git a/api-js/src/organization/objects/organization-connection.js b/api/src/organization/objects/organization-connection.js similarity index 100% rename from api-js/src/organization/objects/organization-connection.js rename to api/src/organization/objects/organization-connection.js diff --git a/api-js/src/organization/objects/organization-error.js b/api/src/organization/objects/organization-error.js similarity index 100% rename from api-js/src/organization/objects/organization-error.js rename to api/src/organization/objects/organization-error.js diff --git a/api-js/src/organization/objects/organization-result.js b/api/src/organization/objects/organization-result.js similarity index 100% rename from api-js/src/organization/objects/organization-result.js rename to api/src/organization/objects/organization-result.js diff --git a/api/src/organization/objects/organization-summary.js b/api/src/organization/objects/organization-summary.js new file mode 100644 index 0000000000..dbeaa82489 --- /dev/null +++ b/api/src/organization/objects/organization-summary.js @@ -0,0 +1,187 @@ +import { GraphQLObjectType } from 'graphql' + +import { categorizedSummaryType } from '../../summaries' +import { GraphQLDate } from 'graphql-scalars' +import { guidanceTagOrder, guidanceTagConnection } from '../../guidance-tag' + +const calculatePercentage = (numerator, denominator) => { + if (denominator <= 0) { + return 0 + } else { + return Number(((numerator / denominator) * 100).toFixed(1)) + } +} + +const createCategory = (name, count, total) => { + return { + name, + count, + percentage: calculatePercentage(count, total), + } +} + +export const organizationSummaryType = new GraphQLObjectType({ + name: 'OrganizationSummary', + description: 'Summaries based on domains that the organization has claimed.', + fields: () => ({ + date: { + type: GraphQLDate, + description: 'Date that the summary was computed.', + resolve: ({ date }) => date, + }, + dmarc: { + type: categorizedSummaryType, + description: 'Summary based on DMARC scan results for a given organization.', + resolve: ({ dmarc }, _) => { + const categories = [ + createCategory('pass', dmarc.pass, dmarc.total), + createCategory('fail', dmarc.fail, dmarc.total), + ] + + return { + categories, + total: dmarc.total, + } + }, + }, + https: { + type: categorizedSummaryType, + description: 'Summary based on HTTPS scan results for a given organization.', + resolve: ({ https }, _) => { + const categories = [ + createCategory('pass', https.pass, https.total), + createCategory('fail', https.fail, https.total), + ] + + return { + categories, + total: https.total, + } + }, + }, + mail: { + type: categorizedSummaryType, + description: 'Summary based on mail scan results for a given organization.', + resolve: ({ mail }, _) => { + const categories = [ + createCategory('pass', mail.pass, mail.total), + createCategory('fail', mail.fail, mail.total), + ] + + return { + categories, + total: mail.total, + } + }, + }, + web: { + type: categorizedSummaryType, + description: 'Summary based on web scan results for a given organization.', + resolve: ({ web }, _) => { + const categories = [createCategory('pass', web.pass, web.total), createCategory('fail', web.fail, web.total)] + + return { + categories, + total: web.total, + } + }, + }, + dmarcPhase: { + type: categorizedSummaryType, + description: 'Summary based on DMARC phases for a given organization.', + resolve: ({ dmarc_phase: dmarcPhase }, _) => { + const categories = [ + createCategory('assess', dmarcPhase.assess, dmarcPhase.total), + createCategory('deploy', dmarcPhase.deploy, dmarcPhase.total), + createCategory('enforce', dmarcPhase.enforce, dmarcPhase.total), + createCategory('maintain', dmarcPhase.maintain, dmarcPhase.total), + ] + + return { + categories, + total: dmarcPhase.total, + } + }, + }, + ssl: { + type: categorizedSummaryType, + description: 'Summary based on SSL scan results for a given organization.', + resolve: ({ ssl }, _) => { + const categories = [createCategory('pass', ssl.pass, ssl.total), createCategory('fail', ssl.fail, ssl.total)] + + return { + categories, + total: ssl.total, + } + }, + }, + webConnections: { + type: categorizedSummaryType, + description: 'Summary based on HTTPS and HSTS scan results for a given organization.', + resolve: ({ web_connections: webConnections }, _) => { + const categories = [ + createCategory('pass', webConnections.pass, webConnections.total), + createCategory('fail', webConnections.fail, webConnections.total), + ] + + return { + categories, + total: webConnections.total, + } + }, + }, + spf: { + type: categorizedSummaryType, + description: 'Summary based on SPF scan results for a given organization.', + resolve: ({ spf }, _) => { + const categories = [createCategory('pass', spf.pass, spf.total), createCategory('fail', spf.fail, spf.total)] + + return { + categories, + total: spf.total, + } + }, + }, + dkim: { + type: categorizedSummaryType, + description: 'Summary based on DKIM scan results for a given organization.', + resolve: ({ dkim }, _) => { + const categories = [ + createCategory('pass', dkim.pass, dkim.total), + createCategory('fail', dkim.fail, dkim.total), + ] + + return { + categories, + total: dkim.total, + } + }, + }, + negativeFindings: { + type: guidanceTagConnection, + description: 'Aggregated negative findings for a given organization.', + args: { + orderBy: { + type: guidanceTagOrder, + description: 'Ordering options for guidance tag connections.', + }, + }, + resolve: async ( + { negative_tags: negativeTags }, + args, + { auth: { loginRequiredBool, userRequired, verifiedRequired }, dataSources: { guidanceTag } }, + ) => { + if (loginRequiredBool) { + const user = await userRequired() + verifiedRequired({ user }) + } + + const guidanceTags = await guidanceTag.summaryConnectionsByTagId({ + guidanceTags: negativeTags, + ...args, + }) + return guidanceTags + }, + }, + }), +}) diff --git a/api/src/organization/objects/organization.js b/api/src/organization/objects/organization.js new file mode 100644 index 0000000000..9dc814967b --- /dev/null +++ b/api/src/organization/objects/organization.js @@ -0,0 +1,381 @@ +import { t } from '@lingui/macro' +import { GraphQLBoolean, GraphQLInt, GraphQLObjectType, GraphQLString, GraphQLList, GraphQLNonNull } from 'graphql' +import { connectionArgs, globalIdField } from 'graphql-relay' + +import { organizationSummaryType } from './organization-summary' +import { nodeInterface } from '../../node' +import { Acronym, Slug } from '../../scalars' +import { affiliationUserOrder } from '../../affiliation/inputs' +import { affiliationConnection } from '../../affiliation/objects' +import { domainOrder, domainFilter } from '../../domain/inputs' +import { domainConnection } from '../../domain/objects' +import { OrderDirection } from '../../enums' +import { tagType } from '../../tags/objects' +import ac from '../../access-control' + +export const organizationType = new GraphQLObjectType({ + name: 'Organization', + fields: () => ({ + id: globalIdField('organization'), + acronym: { + type: Acronym, + description: 'The organizations acronym.', + resolve: ({ acronym }) => acronym, + }, + name: { + type: GraphQLString, + description: 'The full name of the organization.', + resolve: ({ name }) => name, + }, + slug: { + type: Slug, + description: 'Slugified name of the organization.', + resolve: ({ slug }) => slug, + }, + zone: { + type: GraphQLString, + description: 'The zone which the organization belongs to.', + resolve: ({ zone }) => zone, + }, + sector: { + type: GraphQLString, + description: 'The sector which the organization belongs to.', + resolve: ({ sector }) => sector, + }, + country: { + type: GraphQLString, + description: 'The country in which the organization resides.', + resolve: ({ country }) => country, + }, + province: { + type: GraphQLString, + description: 'The province in which the organization resides.', + resolve: ({ province }) => province, + }, + city: { + type: GraphQLString, + description: 'The city in which the organization resides.', + resolve: ({ city }) => city, + }, + verified: { + type: GraphQLBoolean, + description: 'Whether the organization is a verified organization.', + resolve: ({ verified }) => verified, + }, + externallyManaged: { + type: GraphQLBoolean, + description: 'Whether the organization is externally managed.', + resolve: ({ externallyManaged }) => externallyManaged, + }, + externalId: { + type: GraphQLString, + description: 'String ID used to identify the organization in an external system.', + resolve: ({ externalId }) => externalId, + }, + availableTags: { + type: new GraphQLList(tagType), + description: '', + args: { + includeGlobal: { + type: GraphQLBoolean, + description: '', + }, + includePending: { + type: GraphQLBoolean, + description: '', + }, + sortDirection: { + type: new GraphQLNonNull(OrderDirection), + description: 'The direction in which to sort the data.', + }, + }, + resolve: async ( + { _key }, + args, + { userKey, auth: { userRequired, loginRequiredBool, verifiedRequired }, loaders: { loadTagsByOrg } }, + ) => { + if (loginRequiredBool) { + const user = await userRequired() + verifiedRequired({ user }) + } + + const orgTags = await loadTagsByOrg({ + orgId: _key, + ...args, + }) + + console.debug(`User: ${userKey} successfully retrieved their org's tags.`) + + return orgTags + }, + }, + summaries: { + type: organizationSummaryType, + description: 'Summaries based on scan types that are preformed on the given organizations domains.', + resolve: ({ summaries }) => summaries, + }, + historicalSummaries: { + type: new GraphQLList(organizationSummaryType), + description: 'Historical summaries based on scan types that are performed on the given organizations domains.', + args: { + startDate: { + type: GraphQLString, + description: 'The start date for the returned data (YYYY-MM-DD).', + }, + endDate: { + type: GraphQLString, + description: 'The end date for the returned data (YYYY-MM-DD).', + }, + sortDirection: { + type: OrderDirection, + description: 'The direction in which to sort the data.', + }, + limit: { + type: GraphQLInt, + description: 'The maximum amount of summaries to be returned.', + }, + }, + resolve: async ( + { _id }, + args, + { + userKey, + auth: { userRequired, loginRequiredBool, verifiedRequired }, + dataSources: { organization: organizationDS }, + }, + ) => { + if (loginRequiredBool) { + const user = await userRequired() + verifiedRequired({ user }) + } + + const historicalSummaries = await organizationDS.summariesByPeriod({ + orgId: _id, + ...args, + }) + + console.info(`User: ${userKey} successfully retrieved their organization summaries.`) + return historicalSummaries + }, + }, + domainCount: { + type: GraphQLInt, + description: 'The number of domains associated with this organization.', + resolve: ({ domainCount }) => domainCount, + }, + toCsv: { + type: GraphQLString, + description: + 'CSV formatted output of all domains in the organization including their email and web scan statuses.', + args: { + filters: { + type: new GraphQLList(domainFilter), + description: 'Filters used to limit domains returned.', + }, + }, + resolve: async ( + { _id }, + args, + { + i18n, + userKey, + request: { ip }, + auth: { userRequired, verifiedRequired }, + dataSources: { auth: authDS, auditLogs, organization: organizationDS }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const permission = await authDS.permissionByOrgId.load(_id) + if (!ac.can(permission).createOwn('csv').granted) { + console.error( + `User "${userKey}" attempted to retrieve CSV output for organization "${_id}". Permission: ${permission}`, + ) + throw new Error(t`Permission Denied: Please contact organization user for help with retrieving this domain.`) + } + + const domains = await organizationDS.domainStatuses({ + orgId: _id, + ...args, + }) + const headers = [ + 'domain', + 'ipAddresses', + 'https', + 'hsts', + 'certificates', + 'protocols', + 'ciphers', + 'curves', + 'spf', + 'dkim', + 'dmarc', + 'phase', + 'tags', + 'assetState', + 'rcode', + 'blocked', + 'wildcardSibling', + 'wildcardEntry', + 'hasEntrustCertificate', + 'top25Vulnerabilities', + 'cvdEnrollmentStatus', + ] + let csvOutput = headers.join(',') + domains.forEach((domainDoc) => { + const csvLine = headers + .map((header) => { + if (['ipAddresses', 'tags', 'top25Vulnerabilities'].includes(header)) { + return `"${domainDoc[header]?.join('|') || []}"` + } else if ( + ['https', 'hsts', 'certificates', 'protocols', 'ciphers', 'curves', 'spf', 'dkim', 'dmarc'].includes( + header, + ) + ) { + return `"${domainDoc?.status[header]}"` + } else if (header === 'phase') { + switch (domainDoc[header]) { + case 'assess': + return i18n._(t`Assess`) + case 'deploy': + return i18n._(t`Deploy`) + case 'enforce': + return i18n._(t`Enforce`) + case 'maintain': + return i18n._(t`Maintain`) + default: + return '' + } + } + return `"${domainDoc[header]}"` + }) + .join(',') + csvOutput += `\n${csvLine}` + }) + + // Get org names to use in activity log + let orgNames + try { + orgNames = await organizationDS.namesById.load(_id) + } catch (err) { + console.error( + `Error occurred when user: ${userKey} attempted to export org: ${_id}. Error while retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to export organization. Please try again.`)) + } + + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + ipAddress: ip, + }, + action: 'export', + target: { + resource: { + en: orgNames.orgNameEN, + fr: orgNames.orgNameFR, + }, + organization: { + id: _id, + name: orgNames.orgNameEN, + }, + resourceType: 'organization', + }, + }) + + return csvOutput + }, + }, + domains: { + type: domainConnection.connectionType, + description: 'The domains which are associated with this organization.', + args: { + orderBy: { + type: domainOrder, + description: 'Ordering options for domain connections.', + }, + ownership: { + type: GraphQLBoolean, + description: 'Limit domains to those that belong to an organization that has ownership.', + }, + search: { + type: GraphQLString, + description: 'String used to search for domains.', + }, + filters: { + type: new GraphQLList(domainFilter), + description: 'Filters used to limit domains returned.', + }, + ...connectionArgs, + }, + resolve: async ( + { _id }, + args, + + { dataSources: { auth: authDS }, loaders: { loadDomainConnectionsByOrgId } }, + ) => { + const permission = await authDS.permissionByOrgId.load(_id) + const connections = await loadDomainConnectionsByOrgId({ + orgId: _id, + permission, + ...args, + }) + return connections + }, + }, + affiliations: { + type: affiliationConnection.connectionType, + description: 'Organization affiliations to various users.', + args: { + orderBy: { + type: affiliationUserOrder, + description: 'Ordering options for affiliation connections.', + }, + search: { + type: GraphQLString, + description: 'String used to search for affiliated users.', + }, + includePending: { + type: GraphQLBoolean, + description: 'Exclude (false) or include only (true) pending affiliations in the results.', + }, + ...connectionArgs, + }, + resolve: async ( + { _id }, + args, + { + i18n, + auth: { loginRequiredBool }, + dataSources: { auth: authDS }, + loaders: { loadAffiliationConnectionsByOrgId }, + }, + ) => { + const permission = await authDS.permissionByOrgId.load(_id) + if (!ac.can(permission).readOwn('affiliation').granted && loginRequiredBool) { + throw new Error(i18n._(t`Cannot query affiliations on organization without admin permission or higher.`)) + } + + const affiliations = await loadAffiliationConnectionsByOrgId({ + orgId: _id, + ...args, + }) + return affiliations + }, + }, + userHasPermission: { + type: GraphQLBoolean, + description: + 'Value that determines if a user is affiliated with an organization, whether through organization affiliation, verified affiliation, or through super admin status.', + resolve: async ({ _id }, _args, { dataSources: { auth: authDS } }) => { + const permission = await authDS.permissionByOrgId.load(_id) + return ['user', 'admin', 'super_admin', 'owner'].includes(permission) + }, + }, + }), + interfaces: [nodeInterface], + description: 'Organization object containing information for a given Organization.', +}) diff --git a/api-js/src/organization/queries/__tests__/find-my-organizations.test.js b/api/src/organization/queries/__tests__/find-my-organizations.test.js similarity index 87% rename from api-js/src/organization/queries/__tests__/find-my-organizations.test.js rename to api/src/organization/queries/__tests__/find-my-organizations.test.js index 85584c42ba..11ab3ea768 100644 --- a/api-js/src/organization/queries/__tests__/find-my-organizations.test.js +++ b/api/src/organization/queries/__tests__/find-my-organizations.test.js @@ -1,17 +1,18 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { checkSuperAdmin, userRequired, verifiedRequired } from '../../../auth' import { loadUserByKey } from '../../../user/loaders' import { loadOrgConnectionsByUserId } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -39,18 +40,21 @@ describe('given findMyOrganizationsQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'french', emailValidated: true, }) @@ -140,9 +144,9 @@ describe('given findMyOrganizationsQuery', () => { describe('user queries for their organizations', () => { describe('in english', () => { it('returns organizations', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyOrganizations(first: 5) { edges { @@ -169,8 +173,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -190,16 +194,15 @@ describe('given findMyOrganizationsQuery', () => { }), verifiedRequired: verifiedRequired({}), }, - loaders: { - loadOrgConnectionsByUserId: loadOrgConnectionsByUserId({ + dataSources: { organization: { connectionsByUserId: loadOrgConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', - }), - }, + }) } }, }, - ) + }) const expectedResponse = { data: { @@ -245,9 +248,7 @@ describe('given findMyOrganizationsQuery', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved their organizations.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved their organizations.`]) }) }) }) @@ -284,9 +285,9 @@ describe('given findMyOrganizationsQuery', () => { describe('user queries for their organizations', () => { describe('in french', () => { it('returns organizations', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyOrganizations(first: 5) { edges { @@ -313,8 +314,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -334,16 +335,15 @@ describe('given findMyOrganizationsQuery', () => { }), verifiedRequired: verifiedRequired({}), }, - loaders: { - loadOrgConnectionsByUserId: loadOrgConnectionsByUserId({ + dataSources: { organization: { connectionsByUserId: loadOrgConnectionsByUserId({ query, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'fr', - }), - }, + }) } }, }, - ) + }) const expectedResponse = { data: { @@ -389,9 +389,7 @@ describe('given findMyOrganizationsQuery', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved their organizations.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved their organizations.`]) }) }) }) @@ -416,13 +414,11 @@ describe('given findMyOrganizationsQuery', () => { }) describe('database error occurs', () => { it('returns an error message', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValueOnce(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValueOnce(new Error('Database error occurred.')) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyOrganizations(first: 5) { edges { @@ -448,8 +444,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -457,23 +453,18 @@ describe('given findMyOrganizationsQuery', () => { userRequired: jest.fn().mockReturnValue({}), verifiedRequired: jest.fn(), }, - loaders: { - loadOrgConnectionsByUserId: loadOrgConnectionsByUserId({ + dataSources: { organization: { connectionsByUserId: loadOrgConnectionsByUserId({ query: mockedQuery, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, - }), - }, + }) } }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to load organization(s). Please try again.', - ), - ] + const error = [new GraphQLError('Unable to load organization(s). Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -499,13 +490,11 @@ describe('given findMyOrganizationsQuery', () => { }) describe('database error occurs', () => { it('returns an error message', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValueOnce(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValueOnce(new Error('Database error occurred.')) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMyOrganizations(first: 5) { edges { @@ -531,8 +520,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -540,23 +529,18 @@ describe('given findMyOrganizationsQuery', () => { userRequired: jest.fn().mockReturnValue({}), verifiedRequired: jest.fn(), }, - loaders: { - loadOrgConnectionsByUserId: loadOrgConnectionsByUserId({ + dataSources: { organization: { connectionsByUserId: loadOrgConnectionsByUserId({ query: mockedQuery, userKey: user._key, cleanseInput, + auth: { loginRequired: true }, language: 'en', i18n, - }), - }, + }) } }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de charger l'organisation (s). Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de charger l'organisation (s). Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api-js/src/organization/queries/__tests__/find-organization-by-slug.test.js b/api/src/organization/queries/__tests__/find-organization-by-slug.test.js similarity index 77% rename from api-js/src/organization/queries/__tests__/find-organization-by-slug.test.js rename to api/src/organization/queries/__tests__/find-organization-by-slug.test.js index aa6ce432d1..cfb39fda7a 100644 --- a/api-js/src/organization/queries/__tests__/find-organization-by-slug.test.js +++ b/api/src/organization/queries/__tests__/find-organization-by-slug.test.js @@ -1,11 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' @@ -14,6 +14,7 @@ import { loadAffiliationConnectionsByOrgId } from '../../../affiliation/loaders' import { loadDomainConnectionsByOrgId } from '../../../domain/loaders' import { loadUserByKey } from '../../../user/loaders' import { loadOrgBySlug, loadOrgByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -41,11 +42,15 @@ describe('given findOrganizationBySlugQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -115,9 +120,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('authorized user queries organization by slug', () => { it('returns organization', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findOrganizationBySlug(orgSlug: "treasury-board-secretariat") { id @@ -133,8 +138,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -148,30 +153,37 @@ describe('given findOrganizationBySlugQuery', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), + loginRequiredBool: true, }, validators: { cleanseInput, + auth: { loginRequiredBool: true }, + }, + dataSources: { + organization: { + byKey: loadOrgByKey(query, 'en'), + bySlug: loadOrgBySlug({ query, language: 'en' }), + }, }, loaders: { - loadOrgByKey: loadOrgByKey(query, 'en'), - loadOrgBySlug: loadOrgBySlug({ query, language: 'en' }), loadUserByKey: loadUserByKey({ query }), loadDomainConnectionsByOrgId: loadDomainConnectionsByOrgId({ query, userKey: user._key, cleanseInput, + auth: { loginRequiredBool: true }, + i18n, + }), + loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequiredBool: true }, i18n, }), - loadAffiliationConnectionsByOrgId: - loadAffiliationConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }), }, }, - ) + }) const expectedResponse = { data: { @@ -190,9 +202,7 @@ describe('given findOrganizationBySlugQuery', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved organization ${org._key}.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved organization ${org._key}.`]) }) }) }) @@ -220,9 +230,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('authorized user queries organization by slug', () => { it('returns organization', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findOrganizationBySlug(orgSlug: "secretariat-conseil-tresor") { id @@ -238,8 +248,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -253,30 +263,36 @@ describe('given findOrganizationBySlugQuery', () => { loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), + loginRequiredBool: true, }, validators: { cleanseInput, + auth: { loginRequiredBool: true }, + }, + dataSources: { + organization: { + byKey: loadOrgByKey(query, 'fr'), + bySlug: loadOrgBySlug({ query, language: 'fr' }), + }, }, loaders: { - loadOrgByKey: loadOrgByKey(query, 'fr'), - loadOrgBySlug: loadOrgBySlug({ query, language: 'fr' }), loadUserByKey: loadUserByKey({ query }), loadDomainConnectionsByOrgId: loadDomainConnectionsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequiredBool: true }, + i18n, + }), + loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId({ query, userKey: user._key, cleanseInput, i18n, }), - loadAffiliationConnectionsByOrgId: - loadAffiliationConnectionsByOrgId({ - query, - userKey: user._key, - cleanseInput, - i18n, - }), }, }, - ) + }) const expectedResponse = { data: { @@ -295,9 +311,7 @@ describe('given findOrganizationBySlugQuery', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved organization ${org._key}.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved organization ${org._key}.`]) }) }) }) @@ -320,9 +334,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('organization can not be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findOrganizationBySlug( orgSlug: "not-treasury-board-secretariat" @@ -339,8 +353,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -353,32 +367,25 @@ describe('given findOrganizationBySlugQuery', () => { }, validators: { cleanseInput, + auth: { loginRequiredBool: true }, }, - loaders: { - loadOrgBySlug: { + dataSources: { organization: { bySlug: { load: jest.fn().mockReturnValue(), - }, - }, + } } }, }, - ) + }) - const error = [ - new GraphQLError( - `No organization with the provided slug could be found.`, - ), - ] + const error = [new GraphQLError(`No organization with the provided slug could be found.`)] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User 123 could not retrieve organization.`, - ]) + expect(consoleOutput).toEqual([`User 123 could not retrieve organization.`]) }) }) describe('user does not belong to organization', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findOrganizationBySlug(orgSlug: "treasury-board-secretariat") { id @@ -393,8 +400,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -404,28 +411,21 @@ describe('given findOrganizationBySlugQuery', () => { _key: 123, }), verifiedRequired: jest.fn(), + loginRequiredBool: true, }, validators: { cleanseInput, }, - loaders: { - loadOrgBySlug: { + dataSources: { organization: { bySlug: { load: jest.fn().mockReturnValue({}), - }, - }, + } } }, }, - ) + }) - const error = [ - new GraphQLError( - `Permission Denied: Could not retrieve specified organization.`, - ), - ] + const error = [new GraphQLError(`Permission Denied: Could not retrieve specified organization.`)] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User 123 could not retrieve organization.`, - ]) + expect(consoleOutput).toEqual([`User 123 could not retrieve organization.`]) }) }) }) @@ -446,9 +446,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('organization can not be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findOrganizationBySlug( orgSlug: "ne-pas-secretariat-conseil-tresor" @@ -465,8 +465,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -479,32 +479,25 @@ describe('given findOrganizationBySlugQuery', () => { }, validators: { cleanseInput, + auth: { loginRequiredBool: true }, }, - loaders: { - loadOrgBySlug: { + dataSources: { organization: { bySlug: { load: jest.fn().mockReturnValue(), - }, - }, + } } }, }, - ) + }) - const error = [ - new GraphQLError( - "Aucune organisation avec le slug fourni n'a pu être trouvée.", - ), - ] + const error = [new GraphQLError("Aucune organisation avec le slug fourni n'a pu être trouvée.")] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User 123 could not retrieve organization.`, - ]) + expect(consoleOutput).toEqual([`User 123 could not retrieve organization.`]) }) }) describe('user does not belong to organization', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findOrganizationBySlug(orgSlug: "secretariat-conseil-tresor") { id @@ -519,8 +512,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -530,28 +523,21 @@ describe('given findOrganizationBySlugQuery', () => { _key: 123, }), verifiedRequired: jest.fn(), + loginRequiredBool: true, }, validators: { cleanseInput, }, - loaders: { - loadOrgBySlug: { + dataSources: { organization: { bySlug: { load: jest.fn().mockReturnValue({}), - }, - }, + } } }, }, - ) + }) - const error = [ - new GraphQLError( - "Permission refusée : Impossible de récupérer l'organisation spécifiée.", - ), - ] + const error = [new GraphQLError("Permission refusée : Impossible de récupérer l'organisation spécifiée.")] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User 123 could not retrieve organization.`, - ]) + expect(consoleOutput).toEqual([`User 123 could not retrieve organization.`]) }) }) }) diff --git a/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js b/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js new file mode 100644 index 0000000000..b8a25f8328 --- /dev/null +++ b/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js @@ -0,0 +1,394 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { checkSuperAdmin, superAdminRequired, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { loadAllOrganizationDomainStatuses } from '../../loaders' +import dbschema from '../../../../database.json' +import { setupI18n } from '@lingui/core' +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given getAllOrganizationDomainStatuses', () => { + // eslint-disable-next-line no-unused-vars + let query, drop, truncate, schema, collections, superAdminOrg, domainOne, domainTwo, i18n, user, orgOne + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + consoleOutput.length = 0 + }) + beforeEach(async () => { + user = await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + superAdminOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'NFED', + sector: 'NTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'NPFED', + sector: 'NPTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + + domainOne = await collections.domains.save({ + domain: 'domain.one', + phase: 'assess', + status: { + https: 'fail', + hsts: 'pass', + certificates: 'pass', + ciphers: 'pass', + curves: 'pass', + protocols: 'pass', + spf: 'pass', + dkim: 'pass', + dmarc: 'pass', + }, + rcode: 'NOERROR', + blocked: false, + wildcardSibling: false, + wildcardEntry: false, + hasEntrustCertificate: false, + cveDetected: false, + }) + domainTwo = await collections.domains.save({ + domain: 'domain.two', + phase: 'deploy', + status: { + https: 'pass', + hsts: 'fail', + certificates: 'pass', + ciphers: 'fail', + curves: 'pass', + protocols: 'fail', + spf: 'pass', + dkim: 'pass', + dmarc: 'fail', + }, + rcode: 'NOERROR', + blocked: false, + wildcardSibling: false, + wildcardEntry: false, + hasEntrustCertificate: false, + cveDetected: false, + }) + + orgOne = await collections.organizations.save({ + orgDetails: { + en: { + name: 'Org One', + acronym: 'OO', + externalId: 'ORG123', + }, + }, + verified: true, + }) + + await collections.claims.save({ + _to: domainOne._id, + _from: orgOne._id, + }) + await collections.claims.save({ + _to: domainTwo._id, + _from: orgOne._id, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + let loginRequiredBool + describe('login is not required', () => { + beforeEach(async () => { + loginRequiredBool = false + }) + describe('the user is not a super admin', () => { + it('returns a permission error', async () => { + const response = await graphql({ + schema, + source: ` + query { + getAllOrganizationDomainStatuses(filters: []) + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { organization: { allDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + }) } }, + }, + }) + const error = [ + new GraphQLError('Permissions error. You do not have sufficient permissions to access this data.'), + ] + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to access controlled functionality without sufficient privileges.`, + ]) + }) + }) + describe('the user is a super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + }) + + it('returns all domain status results', async () => { + const response = await graphql({ + schema, + source: ` + query { + getAllOrganizationDomainStatuses(filters: []) + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { organization: { allDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + language: 'en', + }) } }, + }, + }) + + const expectedResponse = { + data: { + getAllOrganizationDomainStatuses: `domain,orgNames,orgAcronyms,orgExternalIDs,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,phase,rcode,blocked,wildcardSibling,wildcardEntry,hasEntrustCertificate,top25Vulnerabilities +"domain.one","Org One","OO","ORG123","","fail","pass","pass","pass","pass","pass","pass","pass","pass",Assess,"NOERROR","false","false","false","false","" +"domain.two","Org One","OO","ORG123","","pass","fail","pass","fail","pass","fail","pass","pass","fail",Deploy,"NOERROR","false","false","false","false",""`, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved all domain statuses.`]) + }) + }) + }) + describe('login is required', () => { + beforeEach(async () => { + loginRequiredBool = true + }) + describe('the user is not a super admin', () => { + it('returns a permission error', async () => { + const response = await graphql({ + schema, + source: ` + query { + getAllOrganizationDomainStatuses(filters: []) + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { organization: { allDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + }) } }, + }, + }) + const error = [ + new GraphQLError('Permissions error. You do not have sufficient permissions to access this data.'), + ] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to access controlled functionality without sufficient privileges.`, + ]) + }) + }) + describe('the user is a super admin', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: user._id, + permission: 'super_admin', + }) + }) + + it('returns all domain status results', async () => { + const response = await graphql({ + schema, + source: ` + query { + getAllOrganizationDomainStatuses(filters: []) + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({ i18n }), + loginRequiredBool: loginRequiredBool, + }, + dataSources: { organization: { allDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + language: 'en', + }) } }, + }, + }) + const expectedResponse = { + data: { + getAllOrganizationDomainStatuses: `domain,orgNames,orgAcronyms,orgExternalIDs,ipAddresses,https,hsts,certificates,ciphers,curves,protocols,spf,dkim,dmarc,phase,rcode,blocked,wildcardSibling,wildcardEntry,hasEntrustCertificate,top25Vulnerabilities +"domain.one","Org One","OO","ORG123","","fail","pass","pass","pass","pass","pass","pass","pass","pass",Assess,"NOERROR","false","false","false","false","" +"domain.two","Org One","OO","ORG123","","pass","fail","pass","fail","pass","fail","pass","pass","fail",Deploy,"NOERROR","false","false","false","false",""`, + }, + } + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved all domain statuses.`]) + }) + }) + }) +}) diff --git a/api/src/organization/queries/find-my-organizations.js b/api/src/organization/queries/find-my-organizations.js new file mode 100644 index 0000000000..507de5032f --- /dev/null +++ b/api/src/organization/queries/find-my-organizations.js @@ -0,0 +1,62 @@ +import { GraphQLBoolean, GraphQLString } from 'graphql' +import { connectionArgs } from 'graphql-relay' + +import { organizationOrder } from '../inputs' +import { organizationConnection } from '../objects' + +export const findMyOrganizations = { + type: organizationConnection.connectionType, + description: 'Select organizations a user has access to.', + args: { + orderBy: { + type: organizationOrder, + description: 'Ordering options for organization connections', + }, + search: { + type: GraphQLString, + description: 'String argument used to search for organizations.', + }, + isAdmin: { + type: GraphQLBoolean, + description: 'Filter orgs based off of the user being an admin of them.', + }, + includeSuperAdminOrg: { + type: GraphQLBoolean, + description: 'Filter org list to either include or exclude the super admin org.', + }, + isVerified: { + type: GraphQLBoolean, + description: 'Filter org list to include only verified organizations.', + }, + isAffiliated: { + type: GraphQLBoolean, + description: 'Filter the results based on the users affiliation.', + }, + ...connectionArgs, + }, + resolve: async ( + _, + args, + { + userKey, + auth: { checkSuperAdmin, userRequired, verifiedRequired, loginRequiredBool }, + dataSources: { organization: organizationDS }, + }, + ) => { + if (loginRequiredBool) { + const user = await userRequired() + verifiedRequired({ user }) + } + + const isSuperAdmin = await checkSuperAdmin() + + const orgConnections = await organizationDS.connectionsByUserId({ + isSuperAdmin, + ...args, + }) + + console.info(`User ${userKey} successfully retrieved their organizations.`) + + return orgConnections + }, +} diff --git a/api/src/organization/queries/find-organization-by-slug.js b/api/src/organization/queries/find-organization-by-slug.js new file mode 100644 index 0000000000..90919afe5a --- /dev/null +++ b/api/src/organization/queries/find-organization-by-slug.js @@ -0,0 +1,59 @@ +import { GraphQLNonNull } from 'graphql' +import { t } from '@lingui/macro' +import { Slug } from '../../scalars' +import ac from '../../access-control' + +const { organizationType } = require('../objects') + +export const findOrganizationBySlug = { + type: organizationType, + description: 'Select all information on a selected organization that a user has access to.', + args: { + orgSlug: { + type: new GraphQLNonNull(Slug), + description: 'The slugified organization name you want to retrieve data for.', + }, + }, + resolve: async ( + _, + args, + { + i18n, + userKey, + auth: { checkPermission, userRequired, verifiedRequired, loginRequiredBool }, + dataSources: { organization: organizationDS }, + validators: { cleanseInput }, + }, + ) => { + if (loginRequiredBool) { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + } + + // Cleanse input + const orgSlug = cleanseInput(args.orgSlug) + + // Retrieve organization by slug + const org = await organizationDS.bySlug.load(orgSlug) + + if (typeof org === 'undefined') { + console.warn(`User ${userKey} could not retrieve organization.`) + throw new Error(i18n._(t`No organization with the provided slug could be found.`)) + } + + // Check user permission for organization access + const permission = await checkPermission({ orgId: org._id }) + + if (loginRequiredBool && !ac.can(permission).readOwn('organization').granted) { + console.warn(`User ${userKey} could not retrieve organization.`) + throw new Error(i18n._(t`Permission Denied: Could not retrieve specified organization.`)) + } else if (!loginRequiredBool && org.verified !== true && !ac.can(permission).readOwn('organization').granted) { + throw new Error(i18n._(t`Permission Denied: Could not retrieve specified organization.`)) + } + + console.info(`User ${userKey} successfully retrieved organization ${org._key}.`) + org.id = org._key + return org + }, +} diff --git a/api/src/organization/queries/get-all-organization-domain-statuses.js b/api/src/organization/queries/get-all-organization-domain-statuses.js new file mode 100644 index 0000000000..f982c98a2c --- /dev/null +++ b/api/src/organization/queries/get-all-organization-domain-statuses.js @@ -0,0 +1,88 @@ +import { GraphQLString, GraphQLList } from 'graphql' +import { domainFilter } from '../../domain/inputs' +import { i18n } from '@lingui/core' +import { t } from '@lingui/macro' + +export const getAllOrganizationDomainStatuses = { + type: GraphQLString, + description: 'CSV formatted output of all domains in all organizations including their email and web scan statuses.', + args: { + filters: { + type: new GraphQLList(domainFilter), + description: 'Filters used to limit domains returned.', + }, + }, + resolve: async ( + _, + args, + { + userKey, + auth: { checkSuperAdmin, userRequired, verifiedRequired, superAdminRequired }, + dataSources: { organization: organizationDS }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + const domainStatuses = await organizationDS.allDomainStatuses({ ...args }) + + console.info(`User ${userKey} successfully retrieved all domain statuses.`) + + if (domainStatuses === undefined) return domainStatuses + + const headers = [ + 'domain', + 'orgNames', + 'orgAcronyms', + 'orgExternalIDs', + 'ipAddresses', + 'https', + 'hsts', + 'certificates', + 'ciphers', + 'curves', + 'protocols', + 'spf', + 'dkim', + 'dmarc', + 'phase', + 'rcode', + 'blocked', + 'wildcardSibling', + 'wildcardEntry', + 'hasEntrustCertificate', + 'top25Vulnerabilities', + ] + let csvOutput = headers.join(',') + domainStatuses.forEach((domainStatus) => { + const csvLine = headers + .map((header) => { + if (['orgNames', 'orgAcronyms', 'orgExternalIDs', 'ipAddresses', 'top25Vulnerabilities'].includes(header)) { + return `"${domainStatus[header]?.join('|') || []}"` + } + if (header === 'phase') { + switch (domainStatus[header]) { + case 'assess': + return i18n._(t`Assess`) + case 'deploy': + return i18n._(t`Deploy`) + case 'enforce': + return i18n._(t`Enforce`) + case 'maintain': + return i18n._(t`Maintain`) + default: + return '' + } + } + return `"${domainStatus[header]}"` + }) + .join(',') + csvOutput += `\n${csvLine}` + }) + + return csvOutput + }, +} diff --git a/api/src/organization/queries/index.js b/api/src/organization/queries/index.js new file mode 100644 index 0000000000..c34648f5db --- /dev/null +++ b/api/src/organization/queries/index.js @@ -0,0 +1,3 @@ +export * from './find-my-organizations' +export * from './find-organization-by-slug' +export * from './get-all-organization-domain-statuses' diff --git a/api-js/src/organization/unions/__tests__/create-organization-union.test.js b/api/src/organization/unions/__tests__/create-organization-union.test.js similarity index 92% rename from api-js/src/organization/unions/__tests__/create-organization-union.test.js rename to api/src/organization/unions/__tests__/create-organization-union.test.js index 0b0325e73a..cffa51ed7f 100644 --- a/api-js/src/organization/unions/__tests__/create-organization-union.test.js +++ b/api/src/organization/unions/__tests__/create-organization-union.test.js @@ -22,9 +22,7 @@ describe('given the createOrganizationUnion', () => { domain: {}, } - expect(createOrganizationUnion.resolveType(obj)).toMatchObject( - organizationType, - ) + expect(createOrganizationUnion.resolveType(obj)).toMatch(organizationType.name) }) }) describe('testing the organizationErrorType', () => { @@ -36,9 +34,7 @@ describe('given the createOrganizationUnion', () => { description: 'text', } - expect(createOrganizationUnion.resolveType(obj)).toMatchObject( - organizationErrorType, - ) + expect(createOrganizationUnion.resolveType(obj)).toMatch(organizationErrorType.name) }) }) }) diff --git a/api-js/src/organization/unions/__tests__/remove-organization-union.test.js b/api/src/organization/unions/__tests__/remove-organization-union.test.js similarity index 92% rename from api-js/src/organization/unions/__tests__/remove-organization-union.test.js rename to api/src/organization/unions/__tests__/remove-organization-union.test.js index ad508809bc..4250db9aa7 100644 --- a/api-js/src/organization/unions/__tests__/remove-organization-union.test.js +++ b/api/src/organization/unions/__tests__/remove-organization-union.test.js @@ -22,9 +22,7 @@ describe('given the removeOrganizationUnion', () => { status: 'status', } - expect(removeOrganizationUnion.resolveType(obj)).toMatchObject( - organizationResultType, - ) + expect(removeOrganizationUnion.resolveType(obj)).toMatch(organizationResultType.name) }) }) describe('testing the organizationErrorType', () => { @@ -36,9 +34,7 @@ describe('given the removeOrganizationUnion', () => { description: 'text', } - expect(removeOrganizationUnion.resolveType(obj)).toMatchObject( - organizationErrorType, - ) + expect(removeOrganizationUnion.resolveType(obj)).toMatch(organizationErrorType.name) }) }) }) diff --git a/api-js/src/organization/unions/__tests__/update-organization-union.test.js b/api/src/organization/unions/__tests__/update-organization-union.test.js similarity index 92% rename from api-js/src/organization/unions/__tests__/update-organization-union.test.js rename to api/src/organization/unions/__tests__/update-organization-union.test.js index f6178bb027..52e384b1b9 100644 --- a/api-js/src/organization/unions/__tests__/update-organization-union.test.js +++ b/api/src/organization/unions/__tests__/update-organization-union.test.js @@ -22,9 +22,7 @@ describe('given the updateOrganizationUnion', () => { domain: {}, } - expect(updateOrganizationUnion.resolveType(obj)).toMatchObject( - organizationType, - ) + expect(updateOrganizationUnion.resolveType(obj)).toMatch(organizationType.name) }) }) describe('testing the organizationErrorType', () => { @@ -36,9 +34,7 @@ describe('given the updateOrganizationUnion', () => { description: 'text', } - expect(updateOrganizationUnion.resolveType(obj)).toMatchObject( - organizationErrorType, - ) + expect(updateOrganizationUnion.resolveType(obj)).toMatch(organizationErrorType.name) }) }) }) diff --git a/api-js/src/organization/unions/__tests__/verify-organization-union.test.js b/api/src/organization/unions/__tests__/verify-organization-union.test.js similarity index 92% rename from api-js/src/organization/unions/__tests__/verify-organization-union.test.js rename to api/src/organization/unions/__tests__/verify-organization-union.test.js index bfe92b7019..280ca20c9b 100644 --- a/api-js/src/organization/unions/__tests__/verify-organization-union.test.js +++ b/api/src/organization/unions/__tests__/verify-organization-union.test.js @@ -22,9 +22,7 @@ describe('given the verifyOrganizationUnion', () => { status: 'status', } - expect(verifyOrganizationUnion.resolveType(obj)).toMatchObject( - organizationResultType, - ) + expect(verifyOrganizationUnion.resolveType(obj)).toMatch(organizationResultType.name) }) }) describe('testing the organizationErrorType', () => { @@ -36,9 +34,7 @@ describe('given the verifyOrganizationUnion', () => { description: 'text', } - expect(verifyOrganizationUnion.resolveType(obj)).toMatchObject( - organizationErrorType, - ) + expect(verifyOrganizationUnion.resolveType(obj)).toMatch(organizationErrorType.name) }) }) }) diff --git a/api-js/src/organization/unions/create-organization-union.js b/api/src/organization/unions/create-organization-union.js similarity index 87% rename from api-js/src/organization/unions/create-organization-union.js rename to api/src/organization/unions/create-organization-union.js index 4b3b19db2f..b1b13571f2 100644 --- a/api-js/src/organization/unions/create-organization-union.js +++ b/api/src/organization/unions/create-organization-union.js @@ -8,9 +8,9 @@ allowing for users to create an organization, and support any errors that may oc types: [organizationErrorType, organizationType], resolveType({ _type }) { if (_type === 'organization') { - return organizationType + return organizationType.name } else { - return organizationErrorType + return organizationErrorType.name } }, }) diff --git a/api-js/src/organization/unions/index.js b/api/src/organization/unions/index.js similarity index 100% rename from api-js/src/organization/unions/index.js rename to api/src/organization/unions/index.js diff --git a/api-js/src/organization/unions/remove-organization-union.js b/api/src/organization/unions/remove-organization-union.js similarity index 86% rename from api-js/src/organization/unions/remove-organization-union.js rename to api/src/organization/unions/remove-organization-union.js index 527a197fac..ce045b77e8 100644 --- a/api-js/src/organization/unions/remove-organization-union.js +++ b/api/src/organization/unions/remove-organization-union.js @@ -9,9 +9,9 @@ and support any errors that may occur`, types: [organizationErrorType, organizationResultType], resolveType({ _type }) { if (_type === 'result') { - return organizationResultType + return organizationResultType.name } else { - return organizationErrorType + return organizationErrorType.name } }, }) diff --git a/api-js/src/organization/unions/update-organization-union.js b/api/src/organization/unions/update-organization-union.js similarity index 87% rename from api-js/src/organization/unions/update-organization-union.js rename to api/src/organization/unions/update-organization-union.js index 3ba080fe72..f6c35b23e3 100644 --- a/api-js/src/organization/unions/update-organization-union.js +++ b/api/src/organization/unions/update-organization-union.js @@ -8,9 +8,9 @@ allowing for users to update an organization, and support any errors that may oc types: [organizationErrorType, organizationType], resolveType({ _type }) { if (_type === 'organization') { - return organizationType + return organizationType.name } else { - return organizationErrorType + return organizationErrorType.name } }, }) diff --git a/api-js/src/organization/unions/verify-organization-union.js b/api/src/organization/unions/verify-organization-union.js similarity index 86% rename from api-js/src/organization/unions/verify-organization-union.js rename to api/src/organization/unions/verify-organization-union.js index 8ce160e57e..d804e256ed 100644 --- a/api-js/src/organization/unions/verify-organization-union.js +++ b/api/src/organization/unions/verify-organization-union.js @@ -9,9 +9,9 @@ and support any errors that may occur`, types: [organizationErrorType, organizationResultType], resolveType({ _type }) { if (_type === 'result') { - return organizationResultType + return organizationResultType.name } else { - return organizationErrorType + return organizationErrorType.name } }, }) diff --git a/api-js/src/query.js b/api/src/query.js similarity index 79% rename from api-js/src/query.js rename to api/src/query.js index 62979acd91..03430825eb 100644 --- a/api-js/src/query.js +++ b/api/src/query.js @@ -8,6 +8,9 @@ import * as summaryQueries from './summaries/queries' import * as userQueries from './user/queries' import * as verifiedDomainQueries from './verified-domains/queries' import * as verifiedOrgQueries from './verified-organizations/queries' +import * as auditLogQueries from './audit-logs/queries' +import * as additionalFindingsQueries from './additional-findings/queries' +import * as tagsQueries from './tags/queries' export const createQuerySchema = () => { return new GraphQLObjectType({ @@ -15,6 +18,8 @@ export const createQuerySchema = () => { fields: () => ({ node: nodeField, nodes: nodesField, + // Audit Log Queries + ...auditLogQueries, // Dmarc Summary Queries ...dmarcSummariesQueries, // Domain Queries @@ -29,6 +34,8 @@ export const createQuerySchema = () => { ...verifiedDomainQueries, // Verified Organization Queries ...verifiedOrgQueries, + ...additionalFindingsQueries, + ...tagsQueries, }), }) } diff --git a/api/src/scalars/__tests__/scalar-cve-id.test.js b/api/src/scalars/__tests__/scalar-cve-id.test.js new file mode 100644 index 0000000000..e63c57fc96 --- /dev/null +++ b/api/src/scalars/__tests__/scalar-cve-id.test.js @@ -0,0 +1,37 @@ +import { CveID } from '../cve-id' +import { GraphQLError } from 'graphql' + +describe('CveID Scalar', () => { + test('valid CVE ID', () => { + const validCveId = 'CVE-2021-12345' + expect(CveID.serialize(validCveId)).toBe(validCveId) + expect(CveID.parseValue(validCveId)).toBe(validCveId) + expect(CveID.parseLiteral({ kind: 'StringValue', value: validCveId })).toBe(validCveId) + }) + + test('invalid CVE ID format', () => { + const invalidCveId = 'INVALID-2021-12345' + expect(() => CveID.serialize(invalidCveId)).toThrow(TypeError) + expect(() => CveID.parseValue(invalidCveId)).toThrow(TypeError) + expect(() => CveID.parseLiteral({ kind: 'StringValue', value: invalidCveId })).toThrow(TypeError) + }) + + test('non-string value', () => { + const nonStringValue = 12345 + expect(() => CveID.serialize(nonStringValue)).toThrow(TypeError) + expect(() => CveID.parseValue(nonStringValue)).toThrow(TypeError) + expect(() => CveID.parseLiteral({ kind: 'IntValue', value: nonStringValue })).toThrow(GraphQLError) + }) + + test('valid CVE ID with different lengths', () => { + const validCveIdShort = 'CVE-2021-1234' + const validCveIdLong = 'CVE-2021-1234567' + expect(CveID.serialize(validCveIdShort)).toBe(validCveIdShort) + expect(CveID.parseValue(validCveIdShort)).toBe(validCveIdShort) + expect(CveID.parseLiteral({ kind: 'StringValue', value: validCveIdShort })).toBe(validCveIdShort) + + expect(CveID.serialize(validCveIdLong)).toBe(validCveIdLong) + expect(CveID.parseValue(validCveIdLong)).toBe(validCveIdLong) + expect(CveID.parseLiteral({ kind: 'StringValue', value: validCveIdLong })).toBe(validCveIdLong) + }) +}) diff --git a/api-js/src/scalars/__tests__/scalar-domain.test.js b/api/src/scalars/__tests__/scalar-domain.test.js similarity index 86% rename from api-js/src/scalars/__tests__/scalar-domain.test.js rename to api/src/scalars/__tests__/scalar-domain.test.js index d2e2aee7ec..515a91114b 100644 --- a/api-js/src/scalars/__tests__/scalar-domain.test.js +++ b/api/src/scalars/__tests__/scalar-domain.test.js @@ -1,4 +1,4 @@ -import { Kind } from 'graphql' +import { GraphQLError, Kind } from 'graphql' import { stringify } from 'jest-matcher-utils' import { Domain } from '../index' @@ -28,9 +28,7 @@ describe('given a domain scalar', () => { }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Domain.serialize(invalidInput)).toThrow( new TypeError(`Value is not a string: ${typeof invalidInput}`), ) @@ -52,16 +50,12 @@ describe('given a domain scalar', () => { }) describe('given an invalid domain', () => { const testDomain = 'not an domain' - expect(() => Domain.parseValue(testDomain)).toThrow( - new TypeError(`Value is not a valid domain: ${testDomain}`), - ) + expect(() => Domain.parseValue(testDomain)).toThrow(new TypeError(`Value is not a valid domain: ${testDomain}`)) }) }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Domain.parseValue(invalidInput)).toThrow( new TypeError(`Value is not a string: ${typeof invalidInput}`), ) @@ -110,13 +104,9 @@ describe('given a domain scalar', () => { kind: Kind.DOCUMENT, }, ].forEach((literal) => { - it(`throws an error when parsing invalid literal ${stringify( - literal, - )}`, () => { + it(`throws an error when parsing invalid literal ${stringify(literal)}`, () => { expect(() => Domain.parseLiteral(literal, {})).toThrow( - new TypeError( - `Can only validate strings as domains but got a: ${literal.kind}`, - ), + new GraphQLError(`Can only validate strings as domains but got a: ${literal.kind}`), ) }) }) diff --git a/api-js/src/scalars/__tests__/scalar-organization-acronym.test.js b/api/src/scalars/__tests__/scalar-organization-acronym.test.js similarity index 83% rename from api-js/src/scalars/__tests__/scalar-organization-acronym.test.js rename to api/src/scalars/__tests__/scalar-organization-acronym.test.js index 3a0a4cce89..e9ff9c719e 100644 --- a/api-js/src/scalars/__tests__/scalar-organization-acronym.test.js +++ b/api/src/scalars/__tests__/scalar-organization-acronym.test.js @@ -1,4 +1,4 @@ -import { Kind } from 'graphql' +import { GraphQLError, Kind } from 'graphql' import { stringify } from 'jest-matcher-utils' import { Acronym } from '../index' @@ -13,7 +13,7 @@ describe('given a acronym scalar', () => { }) describe('given an invalid acronym', () => { it('throws type error', () => { - const testAcronym = 'not an acronym' + const testAcronym = 'not an acronym!' expect(() => Acronym.serialize(testAcronym)).toThrow( new TypeError(`Value is not a valid acronym: ${testAcronym}`), ) @@ -22,9 +22,7 @@ describe('given a acronym scalar', () => { }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Acronym.serialize(invalidInput)).toThrow( new TypeError(`Value is not string: ${typeof invalidInput}`), ) @@ -39,7 +37,7 @@ describe('given a acronym scalar', () => { expect(Acronym.parseValue(testAcronym)).toEqual(testAcronym) }) describe('given an invalid acronym', () => { - const testAcronym = 'not an acronym' + const testAcronym = 'not an acronym!' expect(() => Acronym.parseValue(testAcronym)).toThrow( new TypeError(`Value is not a valid acronym: ${testAcronym}`), ) @@ -47,9 +45,7 @@ describe('given a acronym scalar', () => { }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Acronym.parseValue(invalidInput)).toThrow( new TypeError(`Value is not string: ${typeof invalidInput}`), ) @@ -68,7 +64,7 @@ describe('given a acronym scalar', () => { expect(Acronym.parseLiteral(testLiteral, {})).toEqual(testAcronym) }) describe('given an invalid acronym', () => { - const testAcronym = 'not an acronym' + const testAcronym = 'not an acronym!' const testLiteral = { kind: Kind.STRING, value: testAcronym, @@ -88,13 +84,9 @@ describe('given a acronym scalar', () => { kind: Kind.DOCUMENT, }, ].forEach((literal) => { - it(`throws an error when parsing invalid literal ${stringify( - literal, - )}`, () => { + it(`throws an error when parsing invalid literal ${stringify(literal)}`, () => { expect(() => Acronym.parseLiteral(literal, {})).toThrow( - new TypeError( - `Can only validate strings as acronyms but got a: ${literal.kind}`, - ), + new GraphQLError(`Can only validate strings as acronyms but got a: ${literal.kind}`), ) }) }) diff --git a/api/src/scalars/__tests__/scalar-selector.test.js b/api/src/scalars/__tests__/scalar-selector.test.js new file mode 100644 index 0000000000..d55c224733 --- /dev/null +++ b/api/src/scalars/__tests__/scalar-selector.test.js @@ -0,0 +1,90 @@ +import { GraphQLError, Kind } from 'graphql' +import { stringify } from 'jest-matcher-utils' +import { Selectors, SelectorsInput } from '../index' + +describe("checking a 'selector' type", () => { + describe('given a selectors scalar', () => { + describe('value parsing', () => { + describe('given valid inputs', () => { + describe('given a valid selector', () => { + it('returns test selector', () => { + const testSelector = 'selector1' + expect(Selectors.parseValue(testSelector)).toEqual(testSelector) + }) + }) + }) + describe('given invalid inputs', () => { + ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { + expect(() => Selectors.parseValue(invalidInput)).toThrow( + new TypeError(`Value is not a string: ${typeof invalidInput}`), + ) + }) + }) + }) + }) + describe('literal parsing', () => { + describe('given valid inputs', () => { + describe('given a valid selector', () => { + it('returns test selector', () => { + const testSelector = 'selector1' + const testLiteral = { + kind: Kind.STRING, + value: testSelector, + } + expect(Selectors.parseLiteral(testLiteral, {})).toEqual(testSelector) + }) + }) + }) + describe('given invalid inputs', () => { + ;[ + { + kind: Kind.FLOAT, + value: '5', + }, + { + kind: Kind.DOCUMENT, + }, + ].forEach((literal) => { + it(`throws an error when parsing invalid literal ${stringify(literal)}`, () => { + expect(() => Selectors.parseLiteral(literal, {})).toThrow( + new GraphQLError(`Can only validate strings as selectors but got a: ${literal.kind}`), + ) + }) + }) + }) + }) + }) +}) + +describe("checking a 'selectorInput' type", () => { + describe('serializing inputs', () => { + describe('given valid inputs', () => { + describe('given a valid selector', () => { + it('returns test selector', () => { + const testSelector = 'selector1' + expect(SelectorsInput.serialize(testSelector)).toEqual(testSelector) + }) + }) + describe('given an invalid selector', () => { + describe('selector contains string', () => { + it('throws an error', () => { + const testSelector = 'This is an invalid selector' + expect(() => SelectorsInput.serialize(testSelector)).toThrow( + new TypeError(`Value is not a valid selector: ${testSelector}`), + ) + }) + }) + }) + }) + describe('given invalid inputs', () => { + ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { + expect(() => SelectorsInput.serialize(invalidInput)).toThrow( + new TypeError(`Value is not a string: ${typeof invalidInput}`), + ) + }) + }) + }) + }) +}) diff --git a/api-js/src/scalars/__tests__/scalar-slug.test.js b/api/src/scalars/__tests__/scalar-slug.test.js similarity index 79% rename from api-js/src/scalars/__tests__/scalar-slug.test.js rename to api/src/scalars/__tests__/scalar-slug.test.js index d179d6e7a1..5a024d33a4 100644 --- a/api-js/src/scalars/__tests__/scalar-slug.test.js +++ b/api/src/scalars/__tests__/scalar-slug.test.js @@ -1,4 +1,4 @@ -import { Kind } from 'graphql' +import { GraphQLError, Kind } from 'graphql' import { stringify } from 'jest-matcher-utils' import { Slug } from '../index' @@ -15,18 +15,14 @@ describe('given a slug scalar', () => { describe('given an invalid slug', () => { it('throws an error', () => { const testSlug = 'This is an invalid slug' - expect(() => Slug.serialize(testSlug)).toThrow( - new TypeError(`Value is not a valid slug: ${testSlug}`), - ) + expect(() => Slug.serialize(testSlug)).toThrow(new TypeError(`Value is not a valid slug: ${testSlug}`)) }) }) }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Slug.serialize(invalidInput)).toThrow( new TypeError(`Value is not string: ${typeof invalidInput}`), ) @@ -46,17 +42,13 @@ describe('given a slug scalar', () => { describe('given an invalid slug', () => { it('throws a type error', () => { const testSlug = 'invalid slug' - expect(() => Slug.parseValue(testSlug)).toThrow( - new TypeError(`Value is not a valid slug: ${testSlug}`), - ) + expect(() => Slug.parseValue(testSlug)).toThrow(new TypeError(`Value is not a valid slug: ${testSlug}`)) }) }) }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when value parsing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when value parsing ${stringify(invalidInput)}`, () => { expect(() => Slug.parseValue(invalidInput)).toThrow( new TypeError(`Value is not string: ${typeof invalidInput}`), ) @@ -96,13 +88,9 @@ describe('given a slug scalar', () => { kind: Kind.DOCUMENT, }, ].forEach((literal) => { - it(`throws an error when parsing invalid literal ${stringify( - literal, - )}`, () => { + it(`throws an error when parsing invalid literal ${stringify(literal)}`, () => { expect(() => Slug.parseLiteral(literal, {})).toThrow( - new TypeError( - `Can only validate strings as slug but got a: ${literal.kind}`, - ), + new GraphQLError(`Can only validate strings as slug but got a: ${literal.kind}`), ) }) }) diff --git a/api-js/src/scalars/__tests__/scalar-year.test.js b/api/src/scalars/__tests__/scalar-year.test.js similarity index 79% rename from api-js/src/scalars/__tests__/scalar-year.test.js rename to api/src/scalars/__tests__/scalar-year.test.js index 370ff37356..7ac751ed3a 100644 --- a/api-js/src/scalars/__tests__/scalar-year.test.js +++ b/api/src/scalars/__tests__/scalar-year.test.js @@ -1,4 +1,4 @@ -import { Kind } from 'graphql' +import { GraphQLError, Kind } from 'graphql' import { stringify } from 'jest-matcher-utils' import { Year } from '../index' @@ -14,17 +14,13 @@ describe('given a year scalar', () => { describe('given an invalid year', () => { it('throws a typeError', () => { const testYear = 'Text' - expect(() => Year.serialize(testYear)).toThrow( - new TypeError(`Value is not a valid year: ${testYear}`), - ) + expect(() => Year.serialize(testYear)).toThrow(new TypeError(`Value is not a valid year: ${testYear}`)) }) }) }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Year.serialize(invalidInput)).toThrow( new TypeError(`Value is not string: ${typeof invalidInput}`), ) @@ -43,16 +39,12 @@ describe('given a year scalar', () => { }) describe('given an invalid year', () => { const testYear = 'Text' - expect(() => Year.parseValue(testYear)).toThrow( - new TypeError(`Value is not a valid year: ${testYear}`), - ) + expect(() => Year.parseValue(testYear)).toThrow(new TypeError(`Value is not a valid year: ${testYear}`)) }) }) describe('given invalid inputs', () => { ;[123, {}, [], null, undefined, true].forEach((invalidInput) => { - it(`throws an error when serializing ${stringify( - invalidInput, - )}`, () => { + it(`throws an error when serializing ${stringify(invalidInput)}`, () => { expect(() => Year.parseValue(invalidInput)).toThrow( new TypeError(`Value is not string: ${typeof invalidInput}`), ) @@ -92,13 +84,9 @@ describe('given a year scalar', () => { kind: Kind.DOCUMENT, }, ].forEach((literal) => { - it(`throws an error when parsing invalid literal ${stringify( - literal, - )}`, () => { + it(`throws an error when parsing invalid literal ${stringify(literal)}`, () => { expect(() => Year.parseLiteral(literal, {})).toThrow( - new TypeError( - `Can only validate strings as year but got a: ${literal.kind}`, - ), + new GraphQLError(`Can only validate strings as year but got a: ${literal.kind}`), ) }) }) diff --git a/api/src/scalars/cve-id.js b/api/src/scalars/cve-id.js new file mode 100644 index 0000000000..5af501d3cc --- /dev/null +++ b/api/src/scalars/cve-id.js @@ -0,0 +1,33 @@ +import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' + +const validate = (value) => { + if (typeof value !== typeof 'string') { + throw new TypeError(`Value is not a string: ${typeof value}`) + } + + const CVE_REGEX = /^CVE-\d{4}-\d{4,7}$/ + + if (!CVE_REGEX.test(value)) { + throw new TypeError(`Value is not a valid CVE ID: ${value}`) + } + + return value +} + +export const CveID = new GraphQLScalarType({ + name: 'CveID', + description: 'String that conforms to a CVE ID structure.', + serialize: validate, + parseValue: validate, + + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new GraphQLError(`Can only validate strings as CVE IDs but got a: ${ast.kind}`) + } + return validate(ast.value) + }, +}) + +module.exports = { + CveID, +} diff --git a/api/src/scalars/domain.js b/api/src/scalars/domain.js new file mode 100644 index 0000000000..c17891b124 --- /dev/null +++ b/api/src/scalars/domain.js @@ -0,0 +1,35 @@ +import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' + +const validate = (value) => { + if (typeof value !== typeof 'string') { + throw new TypeError(`Value is not a string: ${typeof value}`) + } + + value = value.toLowerCase() + + const DOMAIN_REGEX = /^((?=[a-z0-9-_]{1,63}\.)(xn--)?[a-z0-9_]+(-[a-z0-9_]+)*\.)+[a-z]{2,63}$/ + + if (!DOMAIN_REGEX.test(value)) { + throw new TypeError(`Value is not a valid domain: ${value}`) + } + + return value +} + +export const Domain = new GraphQLScalarType({ + name: 'DomainScalar', + description: 'String that conforms to a domain structure.', + serialize: validate, + parseValue: validate, + + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new GraphQLError(`Can only validate strings as domains but got a: ${ast.kind}`) + } + return validate(ast.value) + }, +}) + +module.exports = { + Domain, +} diff --git a/api/src/scalars/filter-value.js b/api/src/scalars/filter-value.js new file mode 100644 index 0000000000..bb3473bc3e --- /dev/null +++ b/api/src/scalars/filter-value.js @@ -0,0 +1,39 @@ +import { GraphQLScalarType, Kind } from 'graphql' +import { filterEnum } from '../enums/system-filter-value' + +// Convert enum name → value into a plain JS object +const enumValueMap = filterEnum.getValues().reduce((acc, { name, value }) => { + acc[name] = value + return acc +}, {}) + +export const FilterValueScalar = new GraphQLScalarType({ + name: 'FilterValue', + description: 'Filter value: either a system-defined enum name or a user-defined tag string.', + serialize(value) { + return value + }, + parseValue(value) { + if (typeof value !== 'string') { + throw new TypeError(`FilterValue must be a string, got ${typeof value}`) + } + + if (Object.prototype.hasOwnProperty.call(enumValueMap, value)) { + return enumValueMap[value] + } + + return value + }, + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new TypeError(`FilterValue must be a string`) + } + + const input = ast.value + if (Object.prototype.hasOwnProperty.call(enumValueMap, input)) { + return enumValueMap[input] + } + + return input + }, +}) diff --git a/api/src/scalars/index.js b/api/src/scalars/index.js new file mode 100644 index 0000000000..47f08dd9fe --- /dev/null +++ b/api/src/scalars/index.js @@ -0,0 +1,6 @@ +export * from './cve-id' +export * from './domain' +export * from './organization-acronym' +export * from './selector' +export * from './slug' +export * from './year' diff --git a/api/src/scalars/organization-acronym.js b/api/src/scalars/organization-acronym.js new file mode 100644 index 0000000000..bc874e9469 --- /dev/null +++ b/api/src/scalars/organization-acronym.js @@ -0,0 +1,31 @@ +import {Kind, GraphQLError, GraphQLScalarType} from 'graphql' + +const validate = (value) => { + const ACRONYM_REGEX = /^[A-Za-z0-9_-]{1,50}$/ + + if (typeof value !== 'string') { + throw new TypeError(`Value is not string: ${typeof value}`) + } + + if (!ACRONYM_REGEX.test(value)) { + throw new TypeError(`Value is not a valid acronym: ${value}`) + } + return value +} + +export const Acronym = new GraphQLScalarType({ + name: 'Acronym', + description: + 'A field whose value consists of upper case or lower case letters or underscores with a length between 1 and 50.', + serialize: validate, + parseValue: validate, + + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new GraphQLError( + `Can only validate strings as acronyms but got a: ${ast.kind}`, + ) + } + return validate(ast.value) + }, +}) diff --git a/api/src/scalars/selector.js b/api/src/scalars/selector.js new file mode 100644 index 0000000000..b7c275875f --- /dev/null +++ b/api/src/scalars/selector.js @@ -0,0 +1,50 @@ +import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' + +const validateSelector = (value) => { + if (typeof value !== typeof 'string') { + throw new TypeError(`Value is not a string: ${typeof value}`) + } + + return value +} + +export const Selectors = new GraphQLScalarType({ + name: 'Selector', + description: 'A field that conforms to a DKIM selector', + serialize: validateSelector, + parseValue: validateSelector, + + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new GraphQLError(`Can only validate strings as selectors but got a: ${ast.kind}`) + } + return validateSelector(ast.value) + }, +}) + +const validateSelectorInput = (value) => { + const SLUG_REGEX = /^(?:[a-zA-Z0-9](\.?[a-zA-Z0-9])*|\*)$/ + if (typeof value !== typeof 'string') { + throw new TypeError(`Value is not a string: ${typeof value}`) + } + if (!SLUG_REGEX.test(value)) { + throw new TypeError(`Value is not a valid selector: ${value}`) + } + + return value +} + +export const SelectorsInput = new GraphQLScalarType({ + name: 'SelectorInput', + description: + 'A field that conforms to a DKIM selector for input. Must be either a single asterisk or a string where only alphanumeric characters and periods are allowed, string must also start and end with alphanumeric characters', + serialize: validateSelectorInput, + parseValue: validateSelectorInput, + + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new GraphQLError(`Can only validate strings as selectors but got a: ${ast.kind}`) + } + return validateSelectorInput(ast.value) + }, +}) diff --git a/api-js/src/scalars/slug.js b/api/src/scalars/slug.js similarity index 91% rename from api-js/src/scalars/slug.js rename to api/src/scalars/slug.js index bd5d27faf0..f66e2ecd16 100644 --- a/api-js/src/scalars/slug.js +++ b/api/src/scalars/slug.js @@ -1,4 +1,4 @@ -import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' +import {Kind, GraphQLError, GraphQLScalarType} from 'graphql' const validate = (value) => { const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/ diff --git a/api-js/src/scalars/year.js b/api/src/scalars/year.js similarity index 91% rename from api-js/src/scalars/year.js rename to api/src/scalars/year.js index e27d969311..e5de802a24 100644 --- a/api-js/src/scalars/year.js +++ b/api/src/scalars/year.js @@ -1,4 +1,4 @@ -import { GraphQLScalarType, Kind, GraphQLError } from 'graphql' +import {GraphQLScalarType, Kind, GraphQLError} from 'graphql' const validate = (value) => { const YEAR_REGEX = /^\d{4}$/ diff --git a/api/src/server.js b/api/src/server.js new file mode 100644 index 0000000000..44a3a92742 --- /dev/null +++ b/api/src/server.js @@ -0,0 +1,107 @@ +import cookieParser from 'cookie-parser' +import cors from 'cors' +import express from 'express' +import { json } from 'body-parser' + +import http from 'http' +import { ApolloServer } from '@apollo/server' +import { expressMiddleware } from '@as-integrations/express4' +import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default' +import compression from 'compression' + +import requestLanguage from 'express-request-language' +import { GraphQLSchema } from 'graphql' +import depthLimit from 'graphql-depth-limit' +import { createComplexityLimitRule } from 'graphql-validation-complexity' + +import { createQuerySchema } from './query' +import { createMutationSchema } from './mutation' +import { FilterValueScalar } from './scalars/filter-value' + +const createSchema = () => + new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + types: [FilterValueScalar], + }) + +const createValidationRules = (maxDepth, complexityCost, scalarCost, objectCost, listFactor) => { + return [ + depthLimit(maxDepth), + createComplexityLimitRule(complexityCost, { + scalarCost, + objectCost, + listFactor, + formatErrorMessage: (cost) => { + console.warn(`User attempted a costly request: ${cost}`) + return `Query error, query is too complex.` + }, + }), + ] +} + +export const Server = async ({ + maxDepth, + complexityCost, + scalarCost, + objectCost, + listFactor, + tracing, + context = {}, +}) => { + const app = express() + const httpServer = http.createServer(app) + const schema = createSchema() + const server = new ApolloServer({ + schema, + validationRules: createValidationRules(maxDepth, complexityCost, scalarCost, objectCost, listFactor), + introspection: true, + tracing, + plugins: [ + // eslint-disable-next-line new-cap + ApolloServerPluginLandingPageLocalDefault(), + ], + }) + await server.start() + app.set('trust proxy', true) + app.use( + '/graphql', + cors(), + cookieParser(), + compression(), + json(), + requestLanguage({ + languages: ['en', 'fr'], + }), + expressMiddleware(server, { + context, + }), + function (err, _req, res, _next) { + res.status(200).json({ + error: { + errors: [ + { + message: err, + locations: [ + { + line: 1, + column: 1, + }, + ], + }, + ], + }, + }) + }, + ) + + app.get('/alive', (_req, res) => { + res.json({ ok: 'yes' }) + }) + + app.get('/ready', (_req, res) => { + res.json({ ok: 'yes' }) + }) + + return httpServer +} diff --git a/api-js/src/setupEnv.js b/api/src/setupEnv.js similarity index 100% rename from api-js/src/setupEnv.js rename to api/src/setupEnv.js diff --git a/api/src/summaries/data-source.js b/api/src/summaries/data-source.js new file mode 100644 index 0000000000..4b534206b5 --- /dev/null +++ b/api/src/summaries/data-source.js @@ -0,0 +1,7 @@ +import { loadChartSummariesByPeriod } from './loaders' + +export class SummariesDataSource { + constructor({ query, userKey, cleanseInput, i18n }) { + this.getConnectionsByPeriod = loadChartSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + } +} diff --git a/api/src/summaries/index.js b/api/src/summaries/index.js new file mode 100644 index 0000000000..fd864e4e36 --- /dev/null +++ b/api/src/summaries/index.js @@ -0,0 +1,4 @@ +export * from './loaders' +export * from './objects' +export * from './queries' +export * from './data-source' diff --git a/api/src/summaries/loaders/__tests__/load-chart-summaries-by-period.test.js b/api/src/summaries/loaders/__tests__/load-chart-summaries-by-period.test.js new file mode 100644 index 0000000000..74827da66c --- /dev/null +++ b/api/src/summaries/loaders/__tests__/load-chart-summaries-by-period.test.js @@ -0,0 +1,51 @@ +import { loadChartSummariesByPeriod } from '../load-chart-summaries-by-period' +import { createI18n } from '../../../create-i18n' + +describe('loadChartSummariesByPeriod', () => { + let query, userKey, cleanseInput + + const i18n = createI18n('en') + + beforeEach(() => { + query = jest.fn() + userKey = 'test-user' + cleanseInput = jest.fn((input) => input) + }) + + it('handles database query errors', async () => { + query.mockRejectedValue(new Error('Database error')) + const loader = loadChartSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + await expect(loader({ startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' })).rejects.toThrow( + 'Unable to load chart summary data. Please try again.', + ) + }) + + it('handles cursor errors', async () => { + query.mockResolvedValue({ + next: jest.fn().mockRejectedValue(new Error('Cursor error')), + }) + const loader = loadChartSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + await expect(loader({ startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' })).rejects.toThrow( + 'Unable to load chart summary data. Please try again.', + ) + }) + + it('returns empty result if no summaries are found', async () => { + query.mockResolvedValue({ + all: jest.fn().mockResolvedValue([]), + }) + const loader = loadChartSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + const result = await loader({ startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' }) + expect(result).toEqual([]) + }) + + it('returns summaries if found', async () => { + const summaries = [{ id: 1, date: '2023-01-01' }] + query.mockResolvedValue({ + all: jest.fn().mockResolvedValue(summaries), + }) + const loader = loadChartSummariesByPeriod({ query, userKey, cleanseInput, i18n }) + const result = await loader({ startDate: '2023-01-01', endDate: '2023-01-31', sortDirection: 'ASC' }) + expect(result).toEqual(summaries) + }) +}) diff --git a/api/src/summaries/loaders/index.js b/api/src/summaries/loaders/index.js new file mode 100644 index 0000000000..427499a436 --- /dev/null +++ b/api/src/summaries/loaders/index.js @@ -0,0 +1 @@ +export * from './load-chart-summaries-by-period' diff --git a/api/src/summaries/loaders/load-chart-summaries-by-period.js b/api/src/summaries/loaders/load-chart-summaries-by-period.js new file mode 100644 index 0000000000..7ffe986673 --- /dev/null +++ b/api/src/summaries/loaders/load-chart-summaries-by-period.js @@ -0,0 +1,64 @@ +import { t } from '@lingui/macro' +import { aql } from 'arangojs' + +export const loadChartSummariesByPeriod = + ({ query, userKey, cleanseInput, i18n }) => + async ({ startDate, endDate, sortDirection = 'ASC', limit }) => { + const cleansedStartDate = startDate ? cleanseInput(startDate) : null + const cleansedEndDate = endDate ? cleanseInput(endDate) : new Date().toISOString() + + const filterUniqueDates = (array) => { + const filteredArray = [] + const dateSet = new Set() + array.forEach((item) => { + if (!dateSet.has(item.date)) { + filteredArray.push(item) + dateSet.add(item.date) + } + }) + return filteredArray + } + + const sortString = aql`SORT summary.date ${sortDirection}` + let startDateFilter = aql`` + if (typeof cleansedStartDate !== 'undefined') { + startDateFilter = aql`FILTER DATE_FORMAT(summary.date, '%yyyy-%mm-%dd') >= DATE_FORMAT(${cleansedStartDate}, '%yyyy-%mm-%dd')` + } + let endDateFilter = aql`` + if (typeof cleansedEndDate !== 'undefined') { + endDateFilter = aql`FILTER DATE_FORMAT(summary.date, '%yyyy-%mm-%dd') <= DATE_FORMAT(${cleansedEndDate}, '%yyyy-%mm-%dd')` + } + let limitString = aql`` + if (typeof limit !== 'undefined') { + limitString = aql`LIMIT ${limit}` + } + + let requestedSummaryInfo + try { + requestedSummaryInfo = await query` + FOR summary IN chartSummaries + ${startDateFilter} + ${endDateFilter} + ${sortString} + ${limitString} + RETURN MERGE({ id: summary._key }, DOCUMENT(summary._id)) + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather chart summaries in loadChartSummariesByPeriod, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load chart summary data. Please try again.`)) + } + + let summariesInfo = [] + try { + summariesInfo = filterUniqueDates(await requestedSummaryInfo.all()) + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather chart summaries in loadChartSummariesByPeriod, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load chart summary data. Please try again.`)) + } + + return summariesInfo + } diff --git a/api-js/src/summaries/objects/__tests__/categorized-summary.test.js b/api/src/summaries/objects/__tests__/categorized-summary.test.js similarity index 93% rename from api-js/src/summaries/objects/__tests__/categorized-summary.test.js rename to api/src/summaries/objects/__tests__/categorized-summary.test.js index 0433930feb..0713d13c64 100644 --- a/api-js/src/summaries/objects/__tests__/categorized-summary.test.js +++ b/api/src/summaries/objects/__tests__/categorized-summary.test.js @@ -7,9 +7,7 @@ describe('given the categorized summary gql object', () => { const demoType = categorizedSummaryType.getFields() expect(demoType).toHaveProperty('categories') - expect(demoType.categories.type).toMatchObject( - GraphQLList(summaryCategoryType), - ) + expect(demoType.categories.type).toMatchObject(new GraphQLList(summaryCategoryType)) }) it('has a total field', () => { const demoType = categorizedSummaryType.getFields() diff --git a/api/src/summaries/objects/__tests__/chart-summary.test.js b/api/src/summaries/objects/__tests__/chart-summary.test.js new file mode 100644 index 0000000000..c00a36dd8e --- /dev/null +++ b/api/src/summaries/objects/__tests__/chart-summary.test.js @@ -0,0 +1,61 @@ +import { GraphQLDate } from 'graphql-scalars' +import { chartSummaryType } from '../chart-summary' +import { categorizedSummaryType } from '../categorized-summary' + +describe('given the summary category gql object', () => { + describe('testing its field definitions', () => { + it('has a date field', () => { + const demoType = chartSummaryType.getFields() + + expect(demoType).toHaveProperty('date') + expect(demoType.date.type).toMatchObject(GraphQLDate) + }) + it('has a https field', () => { + const demoType = chartSummaryType.getFields() + + expect(demoType).toHaveProperty('https') + expect(demoType.https.type).toMatchObject(categorizedSummaryType) + }) + it('has a dmarc field', () => { + const demoType = chartSummaryType.getFields() + + expect(demoType).toHaveProperty('dmarc') + expect(demoType.dmarc.type).toMatchObject(categorizedSummaryType) + }) + }) + describe('testing the field resolvers', () => { + describe('testing the date resolver', () => { + it('returns the resolved value', () => { + const demoType = chartSummaryType.getFields() + + expect(demoType.date.resolve({ date: '2021-01-01' })).toEqual('2021-01-01') + }) + }) + describe('testing the https resolver', () => { + it('returns the resolved value', () => { + const demoType = chartSummaryType.getFields() + + expect(demoType.https.resolve({ https: { pass: 1, fail: 0, total: 1 } })).toEqual({ + categories: [ + { name: 'pass', count: 1, percentage: 100 }, + { name: 'fail', count: 0, percentage: 0 }, + ], + total: 1, + }) + }) + }) + describe('testing the percentage resolver', () => { + it('returns the resolved value', () => { + const demoType = chartSummaryType.getFields() + + expect(demoType.dmarc.resolve({ dmarc: { pass: 1, fail: 0, total: 1 } })).toEqual({ + categories: [ + { name: 'pass', count: 1, percentage: 100 }, + { name: 'fail', count: 0, percentage: 0 }, + ], + total: 1, + }) + }) + }) + }) +}) diff --git a/api-js/src/summaries/objects/__tests__/summary-category.test.js b/api/src/summaries/objects/__tests__/summary-category.test.js similarity index 79% rename from api-js/src/summaries/objects/__tests__/summary-category.test.js rename to api/src/summaries/objects/__tests__/summary-category.test.js index 0dc5220f43..d13b9d3bb8 100644 --- a/api-js/src/summaries/objects/__tests__/summary-category.test.js +++ b/api/src/summaries/objects/__tests__/summary-category.test.js @@ -1,5 +1,5 @@ -import { GraphQLString, GraphQLInt, GraphQLFloat } from 'graphql' -import { summaryCategoryType } from '../summary-category' +import {GraphQLString, GraphQLInt, GraphQLFloat} from 'graphql' +import {summaryCategoryType} from '../summary-category' describe('given the summary category gql object', () => { describe('testing its field definitions', () => { @@ -27,21 +27,21 @@ describe('given the summary category gql object', () => { it('returns the resolved value', () => { const demoType = summaryCategoryType.getFields() - expect(demoType.name.resolve({ name: 'name' })).toEqual('name') + expect(demoType.name.resolve({name: 'name'})).toEqual('name') }) }) describe('testing the count resolver', () => { it('returns the resolved value', () => { const demoType = summaryCategoryType.getFields() - expect(demoType.count.resolve({ count: 5 })).toEqual(5) + expect(demoType.count.resolve({count: 5})).toEqual(5) }) }) describe('testing the percentage resolver', () => { it('returns the resolved value', () => { const demoType = summaryCategoryType.getFields() - expect(demoType.percentage.resolve({ percentage: 5.5 })).toEqual(5.5) + expect(demoType.percentage.resolve({percentage: 5.5})).toEqual(5.5) }) }) }) diff --git a/api-js/src/summaries/objects/categorized-summary.js b/api/src/summaries/objects/categorized-summary.js similarity index 93% rename from api-js/src/summaries/objects/categorized-summary.js rename to api/src/summaries/objects/categorized-summary.js index 44c5fc1790..571ba841b9 100644 --- a/api-js/src/summaries/objects/categorized-summary.js +++ b/api/src/summaries/objects/categorized-summary.js @@ -5,7 +5,7 @@ export const categorizedSummaryType = new GraphQLObjectType({ name: 'CategorizedSummary', fields: () => ({ categories: { - type: GraphQLList(summaryCategoryType), + type: new GraphQLList(summaryCategoryType), description: `List of SummaryCategory objects with data for different computed categories.`, resolve: ({ categories }) => categories, }, diff --git a/api/src/summaries/objects/chart-summary.js b/api/src/summaries/objects/chart-summary.js new file mode 100644 index 0000000000..9dda9c6004 --- /dev/null +++ b/api/src/summaries/objects/chart-summary.js @@ -0,0 +1,61 @@ +import { GraphQLObjectType } from 'graphql' +import { categorizedSummaryType } from './categorized-summary' +import { globalIdField } from 'graphql-relay' +import { GraphQLDate } from 'graphql-scalars' + +const makeCategory = (key) => ({ + type: categorizedSummaryType, + resolve: (parent) => { + const data = parent[key] + const total = data.total + const safe = total > 0 + return { + total, + categories: [ + { name: 'pass', count: data.pass, percentage: safe ? Number(((data.pass / total) * 100).toFixed(1)) : 0 }, + { name: 'fail', count: data.fail, percentage: safe ? Number(((data.fail / total) * 100).toFixed(1)) : 0 }, + ], + } + }, +}) + +export const chartSummaryType = new GraphQLObjectType({ + name: 'ChartSummary', + description: `This object contains the information for each type of summary that has been pre-computed`, + fields: () => ({ + id: globalIdField('chartSummary'), + date: { + type: GraphQLDate, + description: 'Date that the summary was computed.', + resolve: ({ date }) => date, + }, + https: { ...makeCategory('https'), description: 'https summary data' }, + dmarc: { ...makeCategory('dmarc'), description: 'dmarc summary data' }, + mail: { ...makeCategory('mail'), description: 'Summary based on mail scan results for all domains.' }, + web: { ...makeCategory('web'), description: 'Summary based on web scan results for all domains.' }, + ssl: { ...makeCategory('ssl'), description: 'Summary based on SSL scan results for all domains.' }, + webConnections: { + ...makeCategory('web_connections'), + description: 'Summary based on HTTPS and HSTS scan results for all domains.', + }, + spf: { ...makeCategory('spf'), description: 'Summary based on SPF scan results for all domains.' }, + dkim: { ...makeCategory('dkim'), description: 'Summary based on DKIM scan results for all domains.' }, + dmarcPhase: { + type: categorizedSummaryType, + description: 'Summary based on DMARC phases for all domains.', + resolve: ({ dmarc_phase: dmarcPhase }) => { + const total = dmarcPhase.total + const safe = total > 0 + const phaseNames = ['assess', 'deploy', 'enforce', 'maintain'] + return { + total, + categories: phaseNames.map((name) => ({ + name, + count: dmarcPhase[name], + percentage: safe ? Number(((dmarcPhase[name] / total) * 100).toFixed(1)) : 0, + })), + } + }, + }, + }), +}) diff --git a/api/src/summaries/objects/index.js b/api/src/summaries/objects/index.js new file mode 100644 index 0000000000..e4f1752751 --- /dev/null +++ b/api/src/summaries/objects/index.js @@ -0,0 +1,3 @@ +export * from './categorized-summary' +export * from './summary-category' +export * from './chart-summary' diff --git a/api-js/src/summaries/objects/summary-category.js b/api/src/summaries/objects/summary-category.js similarity index 85% rename from api-js/src/summaries/objects/summary-category.js rename to api/src/summaries/objects/summary-category.js index e501bc6d86..4db4340082 100644 --- a/api-js/src/summaries/objects/summary-category.js +++ b/api/src/summaries/objects/summary-category.js @@ -11,17 +11,17 @@ export const summaryCategoryType = new GraphQLObjectType({ name: { type: GraphQLString, description: `Category of computed summary which the other fields relate to.`, - resolve: ({ name }) => name, + resolve: ({name}) => name, }, count: { type: GraphQLInt, description: `Total count of domains that fall into this category.`, - resolve: ({ count }) => count, + resolve: ({count}) => count, }, percentage: { type: GraphQLFloat, description: `Percentage compared to other categories.`, - resolve: ({ percentage }) => percentage, + resolve: ({percentage}) => percentage, }, }), description: `This object contains the information for each type of summary that has been pre-computed`, diff --git a/api/src/summaries/queries/__tests__/find-chart-summaries.test.js b/api/src/summaries/queries/__tests__/find-chart-summaries.test.js new file mode 100644 index 0000000000..81455dd068 --- /dev/null +++ b/api/src/summaries/queries/__tests__/find-chart-summaries.test.js @@ -0,0 +1,331 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey } from '../../../user/loaders' +import { loadChartSummariesByPeriod } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given findMyOrganizationsQuery', () => { + let query, drop, truncate, schema, collections, sum1, sum2, i18n, user + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + beforeEach(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + consoleOutput.length = 0 + }) + describe('given a successful load', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + sum1 = await collections.chartSummaries.save({ + date: '2021-01-01', + https: { + scan_types: ['https'], + pass: 1, + fail: 0, + total: 1, + }, + dmarc: { + scan_types: ['dmarc'], + pass: 1, + fail: 0, + total: 1, + }, + }) + sum2 = await collections.chartSummaries.save({ + date: '2021-01-02', + https: { + scan_types: ['https'], + pass: 1, + fail: 1, + total: 2, + }, + dmarc: { + scan_types: ['dmarc'], + pass: 2, + fail: 0, + total: 2, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given successful retrieval of domains', () => { + describe('user queries for their organizations', () => { + describe('in english', () => { + it('returns chart summaries from january 2021', async () => { + const response = await graphql({ + schema, + source: ` + query { + findChartSummaries(startDate: "2021-01-01", endDate: "2021-01-31", sortDirection: ASC) { + date + dmarc { + categories { + count + name + percentage + } + total + } + https { + total + categories { + count + name + percentage + } + } + id + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + }, + dataSources: { + summaries: { getConnectionsByPeriod: loadChartSummariesByPeriod({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + language: 'en', + }) }, + }, + }, + }) + + const expectedResponse = { + data: { + findChartSummaries: [ + { + id: toGlobalId('chartSummary', sum1._key), + date: '2021-01-01', + https: { + total: 1, + categories: [ + { + count: 1, + name: 'pass', + percentage: 100, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + }, + dmarc: { + total: 1, + categories: [ + { + count: 1, + name: 'pass', + percentage: 100, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + }, + }, + + { + id: toGlobalId('chartSummary', sum2._key), + date: '2021-01-02', + https: { + total: 2, + categories: [ + { + count: 1, + name: 'pass', + percentage: 50, + }, + { + count: 1, + name: 'fail', + percentage: 50, + }, + ], + }, + dmarc: { + total: 2, + categories: [ + { + count: 2, + name: 'pass', + percentage: 100, + }, + { + count: 0, + name: 'fail', + percentage: 0, + }, + ], + }, + }, + ], + }, + } + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully retrieved their chart summaries.`]) + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful load', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('database error occurs', () => { + it('returns an error message', async () => { + const mockedQuery = jest.fn().mockRejectedValueOnce(new Error('Database error occurred.')) + + const response = await graphql({ + schema, + source: ` + query { + findChartSummaries(startDate: "2021-01-01", endDate: "2021-01-31", sortDirection: ASC) { + date + dmarc { + categories { + count + name + percentage + } + total + } + https { + total + categories { + count + name + percentage + } + } + id + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + checkSuperAdmin: jest.fn(), + userRequired: jest.fn().mockReturnValue({}), + verifiedRequired: jest.fn(), + }, + dataSources: { + summaries: { getConnectionsByPeriod: loadChartSummariesByPeriod({ + query: mockedQuery, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + language: 'en', + i18n, + }) }, + }, + }, + }) + + const error = [new GraphQLError('Unable to load chart summary data. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Database error occurred while user: ${user._key} was trying to gather chart summaries in loadChartSummariesByPeriod, error: Error: Database error occurred.`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/find-chart-summaries.js b/api/src/summaries/queries/find-chart-summaries.js new file mode 100644 index 0000000000..0da7b349fa --- /dev/null +++ b/api/src/summaries/queries/find-chart-summaries.js @@ -0,0 +1,42 @@ +import { GraphQLList, GraphQLString, GraphQLInt } from 'graphql' + +import { chartSummaryType } from '../objects' +import { OrderDirection } from '../../enums' + +export const findChartSummaries = { + type: new GraphQLList(chartSummaryType), + description: 'Select chart summaries a user has access to.', + args: { + startDate: { + type: GraphQLString, + description: 'The start date for the returned data (YYYY-MM-DD).', + }, + endDate: { + type: GraphQLString, + description: 'The end date for the returned data (YYYY-MM-DD).', + }, + sortDirection: { + type: OrderDirection, + description: 'The direction in which to sort the data (ASC or DESC).', + }, + limit: { + type: GraphQLInt, + description: 'The maximum amount of summaries to be returned.', + }, + }, + resolve: async ( + _, + args, + { userKey, auth: { userRequired, loginRequiredBool, verifiedRequired }, dataSources: { summaries } }, + ) => { + if (loginRequiredBool) { + const user = await userRequired() + verifiedRequired({ user }) + } + + const summaryConnections = await summaries.getConnectionsByPeriod({ ...args }) + + console.info(`User: ${userKey} successfully retrieved their chart summaries.`) + return summaryConnections + }, +} diff --git a/api/src/summaries/queries/index.js b/api/src/summaries/queries/index.js new file mode 100644 index 0000000000..8002030019 --- /dev/null +++ b/api/src/summaries/queries/index.js @@ -0,0 +1 @@ +export * from './find-chart-summaries' diff --git a/api/src/tags/data-source.js b/api/src/tags/data-source.js new file mode 100644 index 0000000000..5199e29c96 --- /dev/null +++ b/api/src/tags/data-source.js @@ -0,0 +1,106 @@ +import { t } from '@lingui/macro' +import { loadAllTags, loadTagByTagId, loadTagsByOrg } from './loaders' + +export class TagsDataSource { + constructor({ query, userKey, i18n, language, transaction, collections }) { + this._query = query + this._userKey = userKey + this._i18n = i18n + this._transaction = transaction + this._collections = collections + this.all = loadAllTags({ query, userKey, i18n, language }) + this.byTagId = loadTagByTagId({ query, userKey, i18n, language }) + this.byOrg = loadTagsByOrg({ query, userKey, i18n, language }) + } + + // Fetch the raw DB document (label/description as {en, fr} objects, not translated strings). + // Used by mutations that need to read current field values before updating. + async getRaw(tagId) { + const { _query, _userKey, _i18n } = this + let cursor + try { + cursor = await _query` + WITH tags + FOR tag IN tags + FILTER tag.tagId == ${tagId} + RETURN tag + ` + } catch (err) { + console.error(`Database error occurred while retrieving tag: ${tagId} for user: ${_userKey}, err: ${err}`) + throw new Error(_i18n._(t`Unable to update tag. Please try again.`)) + } + try { + return await cursor.next() + } catch (err) { + console.error(`Cursor error occurred while retrieving tag: ${tagId} for user: ${_userKey}, err: ${err}`) + throw new Error(_i18n._(t`Unable to update tag. Please try again.`)) + } + } + + // Insert or update a tag, then return the freshly loaded record. + async create(tag) { + const { _query, _userKey, _i18n, _transaction, _collections } = this + const trx = await _transaction(_collections) + try { + await trx.step( + () => + _query` + UPSERT { tagId: ${tag.tagId} } + INSERT ${tag} + UPDATE ${tag} + IN tags + RETURN NEW + `, + ) + } catch (err) { + console.error(`Transaction step error occurred for user: ${_userKey} when inserting new tag: ${err}`) + await trx.abort() + throw new Error(_i18n._(t`Unable to create tag. Please try again.`)) + } + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${_userKey} was creating tag: ${err}`) + await trx.abort() + throw new Error(_i18n._(t`Unable to create tag. Please try again.`)) + } + this.byTagId.clear(tag.tagId) + return this.byTagId.load(tag.tagId) + } + + // Update an existing tag matched by matchTagId (which may differ from tag.tagId after a label rename), + // then return the freshly loaded record. + async save(matchTagId, tag) { + const { _query, _userKey, _i18n, _transaction, _collections } = this + const trx = await _transaction(_collections) + try { + await trx.step( + async () => + await _query` + WITH tags + UPSERT { tagId: ${matchTagId} } + INSERT ${tag} + UPDATE ${tag} + IN tags + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred when user: ${_userKey} attempted to update tag: ${matchTagId}, error: ${err}`, + ) + await trx.abort() + throw new Error(_i18n._(t`Unable to update tag. Please try again.`)) + } + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred when user: ${_userKey} attempted to update tag: ${matchTagId}, error: ${err}`, + ) + await trx.abort() + throw new Error(_i18n._(t`Unable to update tag. Please try again.`)) + } + this.byTagId.clear(tag.tagId) + return this.byTagId.load(tag.tagId) + } +} diff --git a/api/src/tags/index.js b/api/src/tags/index.js new file mode 100644 index 0000000000..64ebb55b97 --- /dev/null +++ b/api/src/tags/index.js @@ -0,0 +1,6 @@ +export * from './data-source' +export * from './loaders' +export * from './mutations' +export * from './objects' +export * from './queries' +export * from './unions' diff --git a/api/src/tags/loaders/__tests__/load-all-tags.test.js b/api/src/tags/loaders/__tests__/load-all-tags.test.js new file mode 100644 index 0000000000..af52391f74 --- /dev/null +++ b/api/src/tags/loaders/__tests__/load-all-tags.test.js @@ -0,0 +1,180 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { loadAllTags } from '../index' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given a loadAllTags dataloader', () => { + let query, drop, truncate, collections, i18n + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful load', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.tags.save({ + tagId: 'web', + label: { en: 'Web', fr: 'Web' }, + description: { en: '', fr: '' }, + visible: false, + ownership: 'global', + }) + await collections.tags.save({ + tagId: 'new', + label: { en: 'New', fr: 'Nouveau' }, + description: { en: '', fr: '' }, + visible: true, + ownership: 'global', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns only visible tags', async () => { + // Get User From db + const expectedCursor = await query` + FOR tag IN tags + FILTER tag.visible == true + RETURN { + "tagId": tag.tagId, + "label": TRANSLATE('en', tag.label), + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + const expectedTags = await expectedCursor.all() + + const loader = loadAllTags({ query, language: 'en', i18n }) + const tags = await loader({ isVisible: true }) + + expect(tags).toEqual(expectedTags) + }) + it('returns a list of tags', async () => { + const expectedCursor = await query` + FOR tag IN tags + LET label = TRANSLATE('en', tag.label) + SORT label ASC + RETURN { + "tagId": tag.tagId, + "label": label, + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + const expectedTags = await expectedCursor.all() + + const loader = loadAllTags({ query, language: 'en', i18n }) + const tags = await loader({ isVisible: false }) + + expect(tags).toEqual(expectedTags) + }) + }) + describe('given an unsuccessful load', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('database error is raised', () => { + it('returns an error', async () => { + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const loader = loadAllTags({ + query: mockedQuery, + language: 'en', + userKey: '1234', + i18n, + }) + + try { + await loader({ isVisible: false }) + } catch (err) { + expect(err).toEqual(new Error('Unable to load tag(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Database error occurred while user: 1234 was trying to query tags in loadAllTags, Error: Database error occurred.`, + ]) + }) + }) + describe('cursor error is raised', () => { + it('returns an error', async () => { + const cursor = { + all() { + throw new Error('Cursor error occurred.') + }, + } + const mockedQuery = jest.fn().mockReturnValue(cursor) + const loader = loadAllTags({ + query: mockedQuery, + language: 'en', + userKey: '1234', + i18n, + }) + + try { + await loader({ isVisible: false }) + } catch (err) { + expect(err).toEqual(new Error('Unable to load tag(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Cursor error occurred while user: 1234 was trying to gather tags in loadAllTags, Error: Cursor error occurred.`, + ]) + }) + }) + }) +}) diff --git a/api/src/tags/loaders/__tests__/load-tag-by-tag-id.test.js b/api/src/tags/loaders/__tests__/load-tag-by-tag-id.test.js new file mode 100644 index 0000000000..fedc4d9292 --- /dev/null +++ b/api/src/tags/loaders/__tests__/load-tag-by-tag-id.test.js @@ -0,0 +1,188 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { loadTagByTagId } from '../index' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given a loadTagByTagId dataloader', () => { + let query, drop, truncate, collections, i18n + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful load', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.tags.save({ + tagId: 'web', + label: { en: 'Web', fr: 'Web' }, + description: { en: '', fr: '' }, + visible: false, + ownership: 'global', + }) + await collections.tags.save({ + tagId: 'new', + label: { en: 'New', fr: 'Nouveau' }, + description: { en: '', fr: '' }, + visible: true, + ownership: 'global', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('provided a single id', () => { + it('returns a single tag', async () => { + // Get User From db + const expectedCursor = await query` + FOR tag IN tags + FILTER tag.tagId == 'new' + RETURN { + _type: "tag", + "tagId": tag.tagId, + "label": TRANSLATE('en', tag.label), + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + const expectedTag = await expectedCursor.next() + + const loader = loadTagByTagId({ query, language: 'en', i18n }) + const tag = await loader.load(expectedTag.tagId) + + expect(tag).toEqual(expectedTag) + }) + }) + describe('given a list of ids', () => { + it('returns a list of tags', async () => { + const tagIds = [] + const expectedTags = [] + const expectedCursor = await query` + FOR tag IN tags + RETURN { + _type: "tag", + "tagId": tag.tagId, + "label": TRANSLATE('en', tag.label), + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + } + ` + + while (expectedCursor.hasMore) { + const tempTag = await expectedCursor.next() + tagIds.push(tempTag.tagId) + expectedTags.push(tempTag) + } + + const loader = loadTagByTagId({ query, language: 'en', i18n }) + const tags = await loader.loadMany(tagIds) + expect(tags).toEqual(expectedTags) + }) + }) + }) + describe('given an unsuccessful load', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('database error is raised', () => { + it('returns an error', async () => { + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const loader = loadTagByTagId({ + query: mockedQuery, + language: 'en', + userKey: '1234', + i18n, + }) + + try { + await loader.load('1') + } catch (err) { + expect(err).toEqual(new Error('Unable to load tag(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Database error occurred when user: 1234 running loadTagByTagId: Error: Database error occurred.`, + ]) + }) + }) + describe('cursor error is raised', () => { + it('returns an error', async () => { + const cursor = { + forEach() { + throw new Error('Cursor error occurred.') + }, + } + const mockedQuery = jest.fn().mockReturnValue(cursor) + const loader = loadTagByTagId({ + query: mockedQuery, + language: 'en', + userKey: '1234', + i18n, + }) + + try { + await loader.load('1') + } catch (err) { + expect(err).toEqual(new Error('Unable to load tag(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Cursor error occurred when user: 1234 during loadTagByTagId: Error: Cursor error occurred.`, + ]) + }) + }) + }) +}) diff --git a/api/src/tags/loaders/__tests__/load-tags-by-org.test.js b/api/src/tags/loaders/__tests__/load-tags-by-org.test.js new file mode 100644 index 0000000000..7c2140bf6b --- /dev/null +++ b/api/src/tags/loaders/__tests__/load-tags-by-org.test.js @@ -0,0 +1,221 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { loadTagsByOrg } from '../index' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given a loadTagsByOrg dataloader', () => { + let query, drop, truncate, collections, i18n + + const consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.error = mockedError + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful load', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.tags.save({ + tagId: 'web-web', + label: { en: 'Web', fr: 'Web' }, + description: { en: '', fr: '' }, + visible: false, + ownership: 'global', + }) + await collections.tags.save({ + tagId: 'new-nouveau', + label: { en: 'New', fr: 'Nouveau' }, + description: { en: '', fr: '' }, + visible: true, + ownership: 'pending', + organizations: ['test'], + }) + await collections.tags.save({ + tagId: 'test-test', + label: { en: 'Test', fr: 'Test' }, + description: { en: '', fr: '' }, + visible: true, + ownership: 'org', + organizations: ['test'], + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns only org tags', async () => { + // Get User From db + const expectedCursor = await query` + FOR tag IN tags + FILTER tag.visible == true + FILTER 'test' IN tag.organizations + LET label = TRANSLATE('en', tag.label) + SORT label ASC + RETURN { + "tagId": tag.tagId, + "label": label, + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + const expectedTags = await expectedCursor.all() + + const loader = loadTagsByOrg({ query, language: 'en', i18n }) + const tags = await loader({ + orgId: 'test', + includeGlobal: false, + includePending: false, + sortDirection: 'ASC', + }) + + expect(tags).toEqual(expectedTags) + }) + it('returns pending tags', async () => { + const expectedCursor = await query` + FOR tag IN tags + FILTER tag.visible == true + FILTER 'test' IN tag.organizations OR tag.ownership == "pending" + LET label = TRANSLATE('en', tag.label) + SORT label ASC + RETURN { + "tagId": tag.tagId, + "label": label, + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + const expectedTags = await expectedCursor.all() + + const loader = loadTagsByOrg({ query, language: 'en', i18n }) + const tags = await loader({ orgId: 'test', includePending: true, sortDirection: 'ASC' }) + + expect(tags).toEqual(expectedTags) + }) + it('returns global tags', async () => { + const expectedCursor = await query` + FOR tag IN tags + FILTER tag.visible == true + FILTER 'test' IN tag.organizations OR tag.ownership == "global" + LET label = TRANSLATE('en', tag.label) + SORT label ASC + RETURN { + "tagId": tag.tagId, + "label": label, + "description": TRANSLATE('en', tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + const expectedTags = await expectedCursor.all() + + const loader = loadTagsByOrg({ query, language: 'en', i18n }) + const tags = await loader({ orgId: 'test', includeGlobal: true, sortDirection: 'ASC' }) + + expect(tags).toEqual(expectedTags) + }) + }) + describe('given an unsuccessful load', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('database error is raised', () => { + it('returns an error', async () => { + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + const loader = loadTagsByOrg({ + query: mockedQuery, + language: 'en', + userKey: '1234', + i18n, + }) + + try { + await loader({ isVisible: false }) + } catch (err) { + expect(err).toEqual(new Error('Unable to load tag(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Database error occurred while user: 1234 was trying to query tags in loadTagsByOrg, Error: Database error occurred.`, + ]) + }) + }) + describe('cursor error is raised', () => { + it('returns an error', async () => { + const cursor = { + all() { + throw new Error('Cursor error occurred.') + }, + } + const mockedQuery = jest.fn().mockReturnValue(cursor) + const loader = loadTagsByOrg({ + query: mockedQuery, + language: 'en', + userKey: '1234', + i18n, + }) + + try { + await loader({ isVisible: false }) + } catch (err) { + expect(err).toEqual(new Error('Unable to load tag(s). Please try again.')) + } + + expect(consoleOutput).toEqual([ + `Cursor error occurred while user: 1234 was trying to gather tags in loadTagsByOrg, Error: Cursor error occurred.`, + ]) + }) + }) + }) +}) diff --git a/api/src/tags/loaders/index.js b/api/src/tags/loaders/index.js new file mode 100644 index 0000000000..2048f79837 --- /dev/null +++ b/api/src/tags/loaders/index.js @@ -0,0 +1,3 @@ +export * from './load-all-tags' +export * from './load-tag-by-tag-id' +export * from './load-tags-by-org' diff --git a/api/src/tags/loaders/load-all-tags.js b/api/src/tags/loaders/load-all-tags.js new file mode 100644 index 0000000000..5b1256add4 --- /dev/null +++ b/api/src/tags/loaders/load-all-tags.js @@ -0,0 +1,48 @@ +import { aql } from 'arangojs' +import { t } from '@lingui/macro' + +export const loadAllTags = + ({ query, userKey, i18n, language }) => + async ({ isVisible, orgId }) => { + let visibleFilter = aql`` + if (isVisible) { + visibleFilter = aql`FILTER tag.visible == true` + } + + let orgFilter = aql`` + if (orgId) { + orgFilter = aql`FILTER ${orgId} IN tag.organizations` + } + + let cursor + try { + cursor = await query` + FOR tag IN tags + ${visibleFilter} + ${orgFilter} + LET label = TRANSLATE(${language}, tag.label) + SORT label ASC + RETURN { + "tagId": tag.tagId, + "label": label, + "description": TRANSLATE(${language}, tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + } catch (err) { + console.error(`Database error occurred while user: ${userKey} was trying to query tags in loadAllTags, ${err}`) + throw new Error(i18n._(t`Unable to load tag(s). Please try again.`)) + } + + let tagInfo + try { + tagInfo = await cursor.all() + } catch (err) { + console.error(`Cursor error occurred while user: ${userKey} was trying to gather tags in loadAllTags, ${err}`) + throw new Error(i18n._(t`Unable to load tag(s). Please try again.`)) + } + + return tagInfo + } diff --git a/api/src/tags/loaders/load-tag-by-tag-id.js b/api/src/tags/loaders/load-tag-by-tag-id.js new file mode 100644 index 0000000000..b25991c7ad --- /dev/null +++ b/api/src/tags/loaders/load-tag-by-tag-id.js @@ -0,0 +1,39 @@ +import DataLoader from 'dataloader' +import { t } from '@lingui/macro' + +export const loadTagByTagId = ({ query, userKey, i18n, language }) => + new DataLoader(async (tags) => { + let cursor + + try { + cursor = await query` + WITH tags + FOR tag IN tags + FILTER tag.tagId IN ${tags} + RETURN { + _type: "tag", + "tagId": tag.tagId, + "label": TRANSLATE(${language}, tag.label), + "description": TRANSLATE(${language}, tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} running loadTagByTagId: ${err}`) + throw new Error(i18n._(t`Unable to load tag(s). Please try again.`)) + } + + const tagMap = {} + try { + await cursor.forEach((tag) => { + tagMap[tag.tagId] = tag + }) + } catch (err) { + console.error(`Cursor error occurred when user: ${userKey} during loadTagByTagId: ${err}`) + throw new Error(i18n._(t`Unable to load tag(s). Please try again.`)) + } + + return tags.map((tag) => tagMap[tag]) + }) diff --git a/api/src/tags/loaders/load-tags-by-org.js b/api/src/tags/loaders/load-tags-by-org.js new file mode 100644 index 0000000000..e5f5696023 --- /dev/null +++ b/api/src/tags/loaders/load-tags-by-org.js @@ -0,0 +1,48 @@ +import { aql } from 'arangojs' +import { t } from '@lingui/macro' + +export const loadTagsByOrg = + ({ query, userKey, i18n, language }) => + async ({ orgId, includeGlobal, includePending, sortDirection }) => { + let globalFilter = aql`` + if (includeGlobal) { + globalFilter = aql`OR tag.ownership == "global"` + } + + let pendingFilter = aql`` + if (includePending) { + pendingFilter = aql`OR tag.ownership == "pending"` + } + + let cursor + try { + cursor = await query` + FOR tag IN tags + FILTER tag.visible == true + FILTER ${orgId} IN tag.organizations ${globalFilter} ${pendingFilter} + LET label = TRANSLATE(${language}, tag.label) + SORT label ${sortDirection} + RETURN { + "tagId": tag.tagId, + "label": label, + "description": TRANSLATE(${language}, tag.description), + "visible": tag.visible, + "ownership": tag.ownership, + "organizations": tag.organizations, + } + ` + } catch (err) { + console.error(`Database error occurred while user: ${userKey} was trying to query tags in loadTagsByOrg, ${err}`) + throw new Error(i18n._(t`Unable to load tag(s). Please try again.`)) + } + + let tagInfo + try { + tagInfo = await cursor.all() + } catch (err) { + console.error(`Cursor error occurred while user: ${userKey} was trying to gather tags in loadTagsByOrg, ${err}`) + throw new Error(i18n._(t`Unable to load tag(s). Please try again.`)) + } + + return tagInfo + } diff --git a/api/src/tags/mutations/create-tag.js b/api/src/tags/mutations/create-tag.js new file mode 100644 index 0000000000..293dbc2165 --- /dev/null +++ b/api/src/tags/mutations/create-tag.js @@ -0,0 +1,177 @@ +import { GraphQLNonNull, GraphQLID, GraphQLBoolean, GraphQLString } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' +import { createTagUnion } from '../unions' +import { TagOwnershipEnums } from '../../enums' +import ac from '../../access-control' + +export const createTag = new mutationWithClientMutationId({ + name: 'CreateTag', + description: 'Mutation used to create a new label for tagging domains.', + inputFields: () => ({ + labelEn: { + type: new GraphQLNonNull(GraphQLString), + description: 'English label that will be displayed.', + }, + labelFr: { + description: 'French label that will be displayed.', + type: new GraphQLNonNull(GraphQLString), + }, + descriptionEn: { + description: 'English description of what the tag describes about a domain.', + type: GraphQLString, + }, + descriptionFr: { + description: 'French description of what the tag describes about a domain.', + type: GraphQLString, + }, + isVisible: { + description: 'Value used to decide if users should see the tag.', + type: GraphQLBoolean, + }, + ownership: { + description: 'Ownership of the tag, can be `global`, `org`, or `pending`.', + type: new GraphQLNonNull(TagOwnershipEnums), + }, + orgId: { + description: 'The global id of the organization to be affiliated with the tag.', + type: GraphQLID, + }, + }), + outputFields: () => ({ + result: { + type: createTagUnion, + description: '`CreateTagUnion` returning either a `Tag`, or `TagError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + request, + userKey, + auth: { userRequired, verifiedRequired, checkPermission, checkSuperAdmin, superAdminRequired }, + loaders: { loadOrgByKey }, + dataSources: { tags, auditLogs }, + validators: { cleanseInput, slugify }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Cleanse input + const labelEn = cleanseInput(args.labelEn).toLowerCase() + const labelFr = cleanseInput(args.labelFr).toLowerCase() + const descriptionEn = cleanseInput(args.descriptionEn) + const descriptionFr = cleanseInput(args.descriptionFr) + const ownership = cleanseInput(args.ownership) + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + const insertTag = { + tagId: slugify(`${labelEn}-${labelFr}`), + label: { en: labelEn, fr: labelFr }, + description: { + en: descriptionEn || '', + fr: descriptionFr || '', + }, + visible: args?.isVisible ?? true, + ownership, + organizations: [], + } + + const tag = await tags.byTagId.load(insertTag.tagId) + + const isSuperAdmin = await checkSuperAdmin() + if (ownership === 'global') { + superAdminRequired({ user, isSuperAdmin }) + if (typeof tag !== 'undefined') { + console.warn(`User: ${userKey} attempted to create a tag that already exists: ${insertTag.tagId}`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Tag label already in use. Please try again with a different label.`), + } + } + } + + let permission, org + if (ownership === 'org') { + if (typeof orgId === 'undefined') { + console.warn( + `User: ${userKey} attempted to create a tag: ${insertTag.tagId}, however organization-owned tags must have a valid organization.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to create tag, tagId already in use.`), + } + } + + // Check to see if org exists + org = await loadOrgByKey.load(orgId) + if (typeof org === 'undefined') { + console.warn(`User: ${userKey} attempted to create a tag to an organization: ${orgId} that does not exist.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to create tag in unknown organization.`), + } + } + + permission = await checkPermission({ orgId: org._id }) + if (!ac.can(permission).createOwn('tag').granted) { + console.warn( + `User: ${userKey} attempted to create a tag in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with creating tag.`), + } + } + + if (permission !== 'super_admin' && typeof tag === 'undefined') insertTag.ownership = 'pending' + + if (typeof tag === 'undefined') { + insertTag.organizations = [orgId] + } else if (!tag.organizations.includes(orgId)) { + insertTag.organizations = [...tag.organizations, orgId] + } else { + console.warn( + `User: ${userKey} attempted to create a tag in org:${orgId} that already exists: ${insertTag.tagId}`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Tag label already in use. Please try again with a different label.`), + } + } + } + + const returnTag = await tags.create(insertTag) + + console.info(`User: ${userKey} successfully created tag ${returnTag.tagId}`) + + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + role: isSuperAdmin ? 'super_admin' : permission, + ipAddress: request.ip, + }, + action: 'add', + target: { + resource: insertTag.tagId, // name of resource being acted upon + organization: org && { + id: org._key, + name: org.name, + }, + resourceType: 'tag', + }, + }) + + return returnTag + }, +}) diff --git a/api/src/tags/mutations/index.js b/api/src/tags/mutations/index.js new file mode 100644 index 0000000000..09b1b6647c --- /dev/null +++ b/api/src/tags/mutations/index.js @@ -0,0 +1,2 @@ +export * from './create-tag' +export * from './update-tag' diff --git a/api/src/tags/mutations/update-tag.js b/api/src/tags/mutations/update-tag.js new file mode 100644 index 0000000000..bf50c1b3bb --- /dev/null +++ b/api/src/tags/mutations/update-tag.js @@ -0,0 +1,250 @@ +import { GraphQLNonNull, GraphQLBoolean, GraphQLString, GraphQLID } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' +import { updateTagUnion } from '../unions' +import { TagOwnershipEnums } from '../../enums' +import ac from '../../access-control' + +export const updateTag = new mutationWithClientMutationId({ + name: 'UpdateTag', + description: 'Mutation used to update labels for tagging domains.', + inputFields: () => ({ + tagId: { + type: new GraphQLNonNull(GraphQLString), + description: 'A unique identifier for the tag.', + }, + labelEn: { + type: GraphQLString, + description: 'English label that will be displayed.', + }, + labelFr: { + description: 'French label that will be displayed.', + type: GraphQLString, + }, + descriptionEn: { + description: 'English description of what the tag describes about a domain.', + type: GraphQLString, + }, + descriptionFr: { + description: 'French description of what the tag describes about a domain.', + type: GraphQLString, + }, + isVisible: { + description: 'Value used to decide if users should see the tag.', + type: GraphQLBoolean, + }, + ownership: { + type: TagOwnershipEnums, + description: 'Ownership of the tag, can be `global`, `org`, or `pending`.', + }, + orgId: { + description: 'The global id of the organization to be affiliated with the tag.', + type: GraphQLID, + }, + }), + outputFields: () => ({ + result: { + type: updateTagUnion, + description: '`UpdateTagUnion` returning either a `Tag`, or `TagError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + request, + userKey, + auth: { userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired, checkPermission }, + validators: { cleanseInput, slugify }, + loaders: { loadOrgByKey }, + dataSources: { tags, auditLogs }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Cleanse input + const tagId = cleanseInput(args.tagId) + const labelEn = cleanseInput(args.labelEn)?.toLowerCase() + const labelFr = cleanseInput(args.labelFr)?.toLowerCase() + const descriptionEn = cleanseInput(args.descriptionEn) + const descriptionFr = cleanseInput(args.descriptionFr) + const ownership = cleanseInput(args.ownership) + const isVisible = args.isVisible + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + const compareTag = await tags.getRaw(tagId) + + if (typeof compareTag === 'undefined') { + console.warn( + `User: ${userKey} attempted to update tag: ${tagId}, however there is no tag associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update unknown tag.`), + } + } + + const isSuperAdmin = await checkSuperAdmin() + if (['global', 'pending'].includes(compareTag.ownership)) superAdminRequired({ user, isSuperAdmin }) + + let permission, org + if (compareTag.ownership === 'org') { + if (typeof orgId === 'undefined') { + console.warn( + `User: ${userKey} attempted to update a tag: ${compareTag.tagId}, however organization-owned tags must have a valid organization.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update tag, orgId is invalid.`), + } + } + // Check to see if org exists + org = await loadOrgByKey.load(orgId) + if (typeof org === 'undefined') { + console.warn(`User: ${userKey} attempted to update a tag to an organization: ${orgId} that does not exist.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update tag in unknown organization.`), + } + } + + permission = await checkPermission({ orgId: org._id }) + if (!ac.can(permission).updateOwn('tag').granted) { + console.warn( + `User: ${userKey} attempted to update a tag in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with updating tag.`), + } + } + + if (!compareTag.organizations.includes(org._key)) { + console.warn( + `User: ${userKey} attempted to update a tag in: ${org.slug}, however the tag does not belong to this org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with updating tag.`), + } + } + + if (compareTag.organizations.length > 1 && !isSuperAdmin) { + console.warn( + `User: ${userKey} attempted to update a tag in: ${org.slug}, however the tag belongs to more than one org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact super admin for help with updating tag.`), + } + } + } + + const updatedTagId = slugify(`${labelEn || compareTag.label.en}-${labelFr || compareTag.label.fr}`) + + if (tagId !== updatedTagId) { + const existingTag = await tags.byTagId.load(updatedTagId) + if (typeof existingTag !== 'undefined' && !['org', 'pending'].includes(compareTag.ownership)) { + console.warn(`User: ${userKey} attempted to update a tag that already exists: ${updatedTagId}`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Tag label already in use. Please try again with a different label.`), + } + } + } + + // Update tag + const updatedTag = { + tagId: updatedTagId, + label: { + en: labelEn || compareTag.label.en, + fr: labelFr || compareTag.label.fr, + }, + description: { + en: descriptionEn || compareTag.description.en, + fr: descriptionFr || compareTag.description.fr, + }, + visible: typeof isVisible !== 'undefined' ? isVisible : compareTag.visible, + ownership: ownership || compareTag.ownership, + } + + const returnTag = await tags.save(tagId, updatedTag) + + console.info(`User: ${userKey} successfully updated tag: ${tagId}.`) + + const updatedProperties = [] + if (labelEn) { + updatedProperties.push({ + name: 'labelEn', + oldValue: compareTag.label.en, + newValue: labelEn, + }) + } + if (labelFr) { + updatedProperties.push({ + name: 'labelFr', + oldValue: compareTag.label.fr, + newValue: labelFr, + }) + } + if (descriptionEn) { + updatedProperties.push({ + name: 'descriptionEn', + oldValue: compareTag.description.en, + newValue: descriptionEn, + }) + } + if (descriptionFr) { + updatedProperties.push({ + name: 'descriptionFr', + oldValue: compareTag.description.fr, + newValue: descriptionFr, + }) + } + if (typeof isVisible !== 'undefined') { + updatedProperties.push({ + name: 'visible', + oldValue: compareTag.visible, + newValue: isVisible, + }) + } + if (typeof ownership !== 'undefined') { + updatedProperties.push({ + name: 'ownership', + oldValue: compareTag.ownership, + newValue: ownership, + }) + } + + await auditLogs.logActivity({ + initiatedBy: { + id: user._key, + userName: user.userName, + role: isSuperAdmin ? 'super_admin' : permission, + ipAddress: request.ip, + }, + action: 'update', + target: { + resource: updatedTag.tagId, + updatedProperties, + organization: org && { + id: org._key, + name: org.name, + }, // name of resource being acted upon + resourceType: 'tag', // user, org, domain + }, + }) + + return returnTag + }, +}) diff --git a/api/src/tags/objects/index.js b/api/src/tags/objects/index.js new file mode 100644 index 0000000000..f9414f2e00 --- /dev/null +++ b/api/src/tags/objects/index.js @@ -0,0 +1,2 @@ +export * from './tag' +export * from './tag-error' diff --git a/api/src/tags/objects/tag-error.js b/api/src/tags/objects/tag-error.js new file mode 100644 index 0000000000..39f94099ab --- /dev/null +++ b/api/src/tags/objects/tag-error.js @@ -0,0 +1,18 @@ +import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' + +export const tagErrorType = new GraphQLObjectType({ + name: 'TagError', + description: 'This object is used to inform the user if any errors occurred while using a tag mutation.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({ code }) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue that was encountered.', + resolve: ({ description }) => description, + }, + }), +}) diff --git a/api/src/tags/objects/tag.js b/api/src/tags/objects/tag.js new file mode 100644 index 0000000000..6cfa61fe15 --- /dev/null +++ b/api/src/tags/objects/tag.js @@ -0,0 +1,66 @@ +import { GraphQLBoolean, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { TagOwnershipEnums } from '../../enums/tag-ownership' +import { organizationType } from '../../organization/objects' + +export const tagType = new GraphQLObjectType({ + name: 'Tag', + fields: () => ({ + tagId: { + type: GraphQLString, + description: 'A unique identifier for the tag.', + resolve: ({ tagId }) => tagId, + }, + label: { + type: GraphQLString, + description: 'The display name or label of the tag.', + resolve: ({ label }) => label, + }, + description: { + type: GraphQLString, + description: 'A brief description of the tag.', + resolve: ({ description }) => description, + }, + isVisible: { + type: GraphQLBoolean, + description: 'Indicates whether the tag is visible to users.', + resolve: ({ visible }) => visible, + }, + ownership: { + type: TagOwnershipEnums, + description: '', + resolve: ({ ownership }) => ownership, + }, + organizations: { + type: new GraphQLList(organizationType), + description: '', + resolve: async ( + { tagId, organizations, ownership }, + _, + { + userKey, + auth: { userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired }, + loaders: { loadOrgByKey }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + if (ownership === 'global') return [] + + const orgs = [] + for (const orgId of organizations) { + const org = await loadOrgByKey.load(orgId) + if (!org) continue + orgs.push(org) + } + + console.info(`User: ${userKey} successfully retrieved affiliated orgs for tag: ${tagId}.`) + + return orgs + }, + }, + }), +}) diff --git a/api/src/tags/queries/__tests__/find-all-tags.test.js b/api/src/tags/queries/__tests__/find-all-tags.test.js new file mode 100644 index 0000000000..1673b6cbad --- /dev/null +++ b/api/src/tags/queries/__tests__/find-all-tags.test.js @@ -0,0 +1,69 @@ +import { findAllTags } from '../find-all-tags' + +describe('findAllTags', () => { + let tagsAll, userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired, userKey, context, cleanseInput + + beforeEach(() => { + tagsAll = jest.fn() + userRequired = jest.fn() + verifiedRequired = jest.fn() + checkSuperAdmin = jest.fn() + superAdminRequired = jest.fn() + cleanseInput = jest.fn() + + userKey = 'test-user' + context = { + userKey, + dataSources: { tags: { all: tagsAll } }, + loaders: { loadOrgByKey: jest.fn() }, + auth: { userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired }, + validators: { cleanseInput }, + } + }) + + it('should return tags when loadAllTags is successful', async () => { + const tags = [ + { tagId: '1', label: 'Tag1', description: 'Description1', visible: true, ownership: 'global', organizations: [] }, + { + tagId: '2', + label: 'Tag2', + description: 'Description2', + visible: false, + ownership: 'global', + organizations: [], + }, + ] + tagsAll.mockResolvedValue(tags) + + const result = await findAllTags.resolve(null, { isVisible: false }, context) + + expect(tagsAll).toHaveBeenCalledWith({ isVisible: false, orgId: null }) + expect(result).toEqual(tags) + }) + + it('should apply visible filter when isVisible is true', async () => { + const tags = [{ tagId: '1', label: 'Tag1', description: 'Description1', visible: true, ownership: 'global' }] + tagsAll.mockResolvedValue(tags) + + const result = await findAllTags.resolve(null, { isVisible: true }, context) + + expect(tagsAll).toHaveBeenCalledWith({ isVisible: true, orgId: null }) + expect(result).toEqual(tags) + }) + + it('should log a message when tags are successfully retrieved', async () => { + const tags = [{ tagId: '1', label: 'Tag1', description: 'Description1', visible: true, ownership: 'global' }] + tagsAll.mockResolvedValue(tags) + console.info = jest.fn() + + await findAllTags.resolve(null, { isVisible: false }, context) + + expect(console.info).toHaveBeenCalledWith(`User: ${userKey} successfully retrieved tags.`) + }) + + it('should throw an error when loadAllTags fails', async () => { + tagsAll.mockRejectedValue(new Error('Load error')) + + await expect(findAllTags.resolve(null, { isVisible: false }, context)).rejects.toThrow('Load error') + }) +}) diff --git a/api/src/tags/queries/find-all-tags.js b/api/src/tags/queries/find-all-tags.js new file mode 100644 index 0000000000..31b58c411f --- /dev/null +++ b/api/src/tags/queries/find-all-tags.js @@ -0,0 +1,47 @@ +import { GraphQLBoolean, GraphQLList, GraphQLID } from 'graphql' +import { tagType } from '../objects' +import { fromGlobalId } from 'graphql-relay' + +export const findAllTags = { + type: new GraphQLList(tagType), + description: 'All dynamically generated tags users have access to.', + args: { + orgId: { + type: GraphQLID, + description: 'The organization you wish to query the tags from.', + }, + isVisible: { + type: GraphQLBoolean, + description: 'Indicates whether the tag is visible to users.', + }, + }, + resolve: async ( + _, + args, + { + userKey, + auth: { userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired }, + loaders: { loadOrgByKey }, + dataSources: { tags: tagsSource }, + validators: { cleanseInput }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + let orgKey = null + if (args.orgId) { + const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + // Get Org from db + const org = await loadOrgByKey.load(orgId) + orgKey = org?._key + } else { + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + } + + const tags = await tagsSource.all({ ...args, orgId: orgKey }) + console.info(`User: ${userKey} successfully retrieved tags.`) + return tags + }, +} diff --git a/api/src/tags/queries/index.js b/api/src/tags/queries/index.js new file mode 100644 index 0000000000..59cb6ececd --- /dev/null +++ b/api/src/tags/queries/index.js @@ -0,0 +1 @@ +export * from './find-all-tags' diff --git a/api/src/tags/unions/__tests__/create-tag-union.test.js b/api/src/tags/unions/__tests__/create-tag-union.test.js new file mode 100644 index 0000000000..75e4b1f3c3 --- /dev/null +++ b/api/src/tags/unions/__tests__/create-tag-union.test.js @@ -0,0 +1,36 @@ +import { createTagUnion } from '../create-tag-union' +import { tagErrorType, tagType } from '../../objects' +import { GraphQLUnionType } from 'graphql' + +describe('createTagUnion', () => { + it('should be an instance of GraphQLUnionType', () => { + expect(createTagUnion).toBeInstanceOf(GraphQLUnionType) + }) + + it('should have the correct name', () => { + expect(createTagUnion.name).toBe('CreateTagUnion') + }) + + it('should have the correct description', () => { + expect(createTagUnion.description).toBe(`This union is used with the \`CreateTag\` mutation, +allowing for users to create a tag and add it to their org, +and support any errors that may occur`) + }) + + it('should have the correct types', () => { + expect(createTagUnion.getTypes()).toContain(tagErrorType) + expect(createTagUnion.getTypes()).toContain(tagType) + }) + + describe('resolveType', () => { + it('should return tagType name when _type is "tag"', () => { + const result = createTagUnion.resolveType({ _type: 'tag' }) + expect(result).toBe(tagType.name) + }) + + it('should return tagErrorType name when _type is not "tag"', () => { + const result = createTagUnion.resolveType({ _type: 'error' }) + expect(result).toBe(tagErrorType.name) + }) + }) +}) diff --git a/api/src/tags/unions/__tests__/update-tag-union.test.js b/api/src/tags/unions/__tests__/update-tag-union.test.js new file mode 100644 index 0000000000..1d6a640e35 --- /dev/null +++ b/api/src/tags/unions/__tests__/update-tag-union.test.js @@ -0,0 +1,36 @@ +import { updateTagUnion } from '../update-tag-union' +import { tagErrorType, tagType } from '../../objects' +import { GraphQLUnionType } from 'graphql' + +describe('updateTagUnion', () => { + it('should be an instance of GraphQLUnionType', () => { + expect(updateTagUnion).toBeInstanceOf(GraphQLUnionType) + }) + + it('should have the correct name', () => { + expect(updateTagUnion.name).toBe('UpdateTagUnion') + }) + + it('should have the correct description', () => { + expect(updateTagUnion.description).toBe(`This union is used with the \`UpdateTag\` mutation, +allowing for users to update a tag and add it to their org, +and support any errors that may occur`) + }) + + it('should have the correct types', () => { + expect(updateTagUnion.getTypes()).toContain(tagErrorType) + expect(updateTagUnion.getTypes()).toContain(tagType) + }) + + describe('resolveType', () => { + it('should return tagType name when _type is "tag"', () => { + const result = updateTagUnion.resolveType({ _type: 'tag' }) + expect(result).toBe(tagType.name) + }) + + it('should return tagErrorType name when _type is not "tag"', () => { + const result = updateTagUnion.resolveType({ _type: 'error' }) + expect(result).toBe(tagErrorType.name) + }) + }) +}) diff --git a/api/src/tags/unions/create-tag-union.js b/api/src/tags/unions/create-tag-union.js new file mode 100644 index 0000000000..5eccb6cc63 --- /dev/null +++ b/api/src/tags/unions/create-tag-union.js @@ -0,0 +1,17 @@ +import { GraphQLUnionType } from 'graphql' +import { tagErrorType, tagType } from '../objects' + +export const createTagUnion = new GraphQLUnionType({ + name: 'CreateTagUnion', + description: `This union is used with the \`CreateTag\` mutation, +allowing for users to create a tag and add it to their org, +and support any errors that may occur`, + types: [tagErrorType, tagType], + resolveType({ _type }) { + if (_type === 'tag') { + return tagType.name + } else { + return tagErrorType.name + } + }, +}) diff --git a/api/src/tags/unions/index.js b/api/src/tags/unions/index.js new file mode 100644 index 0000000000..9c4ea619c2 --- /dev/null +++ b/api/src/tags/unions/index.js @@ -0,0 +1,2 @@ +export * from '../unions/create-tag-union' +export * from './update-tag-union' diff --git a/api/src/tags/unions/update-tag-union.js b/api/src/tags/unions/update-tag-union.js new file mode 100644 index 0000000000..0aaca32ee2 --- /dev/null +++ b/api/src/tags/unions/update-tag-union.js @@ -0,0 +1,17 @@ +import { GraphQLUnionType } from 'graphql' +import { tagErrorType, tagType } from '../objects' + +export const updateTagUnion = new GraphQLUnionType({ + name: 'UpdateTagUnion', + description: `This union is used with the \`UpdateTag\` mutation, +allowing for users to update a tag and add it to their org, +and support any errors that may occur`, + types: [tagErrorType, tagType], + resolveType({ _type }) { + if (_type === 'tag') { + return tagType.name + } else { + return tagErrorType.name + } + }, +}) diff --git a/api/src/testUtilities.js b/api/src/testUtilities.js new file mode 100644 index 0000000000..548773207c --- /dev/null +++ b/api/src/testUtilities.js @@ -0,0 +1,58 @@ +import { ensure } from 'arango-tools' +import { Database } from 'arangojs' +import { tokenize } from './auth' +import { createContext } from './create-context' + +export async function ensureDatabase(options) { + let variables + if (options.variables) { + variables = options.variables + variables.name = variables.dbname + } else { + variables = { ...options } + } + const systemDatabase = new Database({ url: variables.url, databaseName: '_system' }) + await systemDatabase.login('root', variables.rootPassword) + const databases = await systemDatabase.listDatabases() + if (!databases.includes(variables.name)) { + try { + await systemDatabase.createDatabase(variables.name) + } catch (e) { + console.error(`Failed to create database ${variables.name}: ${e.message}`) + process.exit(1) + } + } + + let ensureOptions + if (options.variables) { + ensureOptions = { + variables: options.variables, + schema: { ...options.schema }, + } + } else { + ensureOptions = options + } + + return await ensure(ensureOptions) +} + +export function createUserContextGenerator({ query, db, transaction, collectionNames, i18n, secret, salt }) { + return async function createUserContext({ userKey, expiry = '60m', language = 'en', loginRequiredBool = true }) { + const signedToken = tokenize({ + expiresIn: expiry, + parameters: { userKey: userKey }, + secret: secret, + }) + return await createContext({ + query, + db, + transaction, + collections: collectionNames, + req: { headers: { authorization: signedToken } }, + i18n, + language: language, + loginRequiredBool: loginRequiredBool, + salt: salt, + }) + } +} diff --git a/api/src/user/index.js b/api/src/user/index.js new file mode 100644 index 0000000000..ee648b9915 --- /dev/null +++ b/api/src/user/index.js @@ -0,0 +1,6 @@ +export * from './loaders' +export * from './mutations' +export * from './objects' +export * from './queries' +export * from './unions' +export * from './inputs' diff --git a/api/src/user/inputs/__tests__/user-order.test.js b/api/src/user/inputs/__tests__/user-order.test.js new file mode 100644 index 0000000000..0bac20decf --- /dev/null +++ b/api/src/user/inputs/__tests__/user-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { userOrder } from '../user-order' +import { OrderDirection, UserOrderField } from '../../../enums' + +describe('given the affiliationOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = userOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = userOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(UserOrderField)) + }) + }) +}) diff --git a/api/src/user/inputs/email-update-options.js b/api/src/user/inputs/email-update-options.js new file mode 100644 index 0000000000..6617b09ecb --- /dev/null +++ b/api/src/user/inputs/email-update-options.js @@ -0,0 +1,22 @@ +import { GraphQLBoolean, GraphQLInputObjectType } from 'graphql' + +export const emailUpdatesInput = new GraphQLInputObjectType({ + name: 'emailUpdatesInput', + fields: () => ({ + orgFootprint: { + type: GraphQLBoolean, + description: + "Value used to determine if user wants to receive possibly daily email updates about their organization's digital footprint.", + }, + progressReport: { + type: GraphQLBoolean, + description: + "Value used to determine if user wants to receive monthly email updates about their organization's compliance score progress.", + }, + detectDecay: { + type: GraphQLBoolean, + description: + "Value used to determine if user wants to receive possibly daily email updates about their organization's compliance statuses.", + }, + }), +}) diff --git a/api/src/user/inputs/index.js b/api/src/user/inputs/index.js new file mode 100644 index 0000000000..7bcd1778ad --- /dev/null +++ b/api/src/user/inputs/index.js @@ -0,0 +1,2 @@ +export * from './user-order' +export * from './email-update-options.js' diff --git a/api/src/user/inputs/user-order.js b/api/src/user/inputs/user-order.js new file mode 100644 index 0000000000..ea691c7c6e --- /dev/null +++ b/api/src/user/inputs/user-order.js @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' + +import { OrderDirection, UserOrderField } from '../../enums' + +export const userOrder = new GraphQLInputObjectType({ + name: 'UserOrder', + description: 'Ordering options for affiliation connections.', + fields: () => ({ + field: { + type: new GraphQLNonNull(UserOrderField), + description: 'The field to order affiliations by.', + }, + direction: { + type: new GraphQLNonNull(OrderDirection), + description: 'The ordering direction.', + }, + }), +}) diff --git a/api-js/src/user/loaders/__tests__/load-user-by-key.test.js b/api/src/user/loaders/__tests__/load-user-by-key.test.js similarity index 83% rename from api-js/src/user/loaders/__tests__/load-user-by-key.test.js rename to api/src/user/loaders/__tests__/load-user-by-key.test.js index 12cee7040d..ee7f164734 100644 --- a/api-js/src/user/loaders/__tests__/load-user-by-key.test.js +++ b/api/src/user/loaders/__tests__/load-user-by-key.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadUserByKey } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -35,25 +36,27 @@ describe('given a loadUserByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) await collections.users.save({ userName: 'random@email.ca', displayName: 'Random Name', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }) @@ -119,9 +122,7 @@ describe('given a loadUserByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadUserByKey({ query: mockedQuery, userKey: '1234', @@ -131,9 +132,7 @@ describe('given a loadUserByKey dataloader', () => { try { await loader.load('1234') } catch (err) { - expect(err).toEqual( - new Error('Unable to load user(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load user(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -158,9 +157,7 @@ describe('given a loadUserByKey dataloader', () => { try { await loader.load('1234') } catch (err) { - expect(err).toEqual( - new Error('Unable to load user(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load user(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -186,9 +183,7 @@ describe('given a loadUserByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadUserByKey({ query: mockedQuery, userKey: '1234', @@ -198,11 +193,7 @@ describe('given a loadUserByKey dataloader', () => { try { await loader.load('1234') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) utilisateur(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) utilisateur(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -227,11 +218,7 @@ describe('given a loadUserByKey dataloader', () => { try { await loader.load('1234') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) utilisateur(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) utilisateur(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/user/loaders/__tests__/load-user-by-username.test.js b/api/src/user/loaders/__tests__/load-user-by-username.test.js similarity index 82% rename from api-js/src/user/loaders/__tests__/load-user-by-username.test.js rename to api/src/user/loaders/__tests__/load-user-by-username.test.js index 45d55db180..e08aa6280d 100644 --- a/api-js/src/user/loaders/__tests__/load-user-by-username.test.js +++ b/api/src/user/loaders/__tests__/load-user-by-username.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadUserByUserName } from '../index' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -34,25 +35,27 @@ describe('given a loadUserByUserName dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) await collections.users.save({ userName: 'random@email.ca', displayName: 'Random Name', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }) @@ -83,10 +86,7 @@ describe('given a loadUserByUserName dataloader', () => { describe('provided a list of usernames', () => { it('returns a list of users', async () => { const expectedUsers = [] - const userNames = [ - 'random@email.ca', - 'test.account@istio.actually.exists', - ] + const userNames = ['random@email.ca', 'test.account@istio.actually.exists'] const loader = loadUserByUserName({ query, i18n }) for (const i in userNames) { @@ -124,17 +124,13 @@ describe('given a loadUserByUserName dataloader', () => { it('throws an error', async () => { const userName = 'random@email.ca' - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadUserByUserName({ query, userKey: '1234', i18n }) try { await loader.load(userName) } catch (err) { - expect(err).toEqual( - new Error('Unable to load user(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load user(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -157,9 +153,7 @@ describe('given a loadUserByUserName dataloader', () => { try { await loader.load(userName) } catch (err) { - expect(err).toEqual( - new Error('Unable to load user(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load user(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -187,19 +181,13 @@ describe('given a loadUserByUserName dataloader', () => { it('throws an error', async () => { const userName = 'random@email.ca' - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadUserByUserName({ query, userKey: '1234', i18n }) try { await loader.load(userName) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) utilisateur(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) utilisateur(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -222,11 +210,7 @@ describe('given a loadUserByUserName dataloader', () => { try { await loader.load(userName) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) utilisateur(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) utilisateur(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api/src/user/loaders/index.js b/api/src/user/loaders/index.js new file mode 100644 index 0000000000..c55f567bd7 --- /dev/null +++ b/api/src/user/loaders/index.js @@ -0,0 +1,4 @@ +export * from './load-my-tracker-by-user-id' +export * from './load-user-by-key' +export * from './load-user-by-username' +export * from './load-user-connections-by-user-id' diff --git a/api/src/user/loaders/load-my-tracker-by-user-id.js b/api/src/user/loaders/load-my-tracker-by-user-id.js new file mode 100644 index 0000000000..343323e5e9 --- /dev/null +++ b/api/src/user/loaders/load-my-tracker-by-user-id.js @@ -0,0 +1,83 @@ +import { t } from '@lingui/macro' + +export const loadMyTrackerByUserId = + ({ query, userKey, i18n }) => + async () => { + const userDBId = `users/${userKey}` + + let requestedDomainInfo + try { + requestedDomainInfo = await query` + WITH users, domains + LET favDomains = ( + FOR v, e IN 1..1 OUTBOUND ${userDBId} favourites + OPTIONS {order: "bfs"} + RETURN { "id": v._key, "phase": v.phase, "https": v.status.https, "dmarc": v.status.dmarc, "_type": "domain" } + ) + RETURN { "domains": favDomains } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query domains in loadDomainsByUser, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to query domain(s). Please try again.`)) + } + + let domainsInfo + try { + domainsInfo = await requestedDomainInfo.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather domains in loadDomainsByUser, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load domain(s). Please try again.`)) + } + + const returnSummaries = { + https: { + pass: 0, + fail: 0, + total: 0, + }, + dmarc: { + pass: 0, + fail: 0, + total: 0, + }, + dmarc_phase: { + assess: 0, + deploy: 0, + enforce: 0, + maintain: 0, + total: 0, + }, + } + + domainsInfo.domains.forEach(({ phase, https, dmarc }) => { + // calculate https summary + if (https === 'pass') { + returnSummaries.https.pass++ + returnSummaries.https.total++ + } else if (https === 'fail') { + returnSummaries.https.fail++ + returnSummaries.https.total++ + } + + // calculate DMARC summary + if (dmarc === 'pass') returnSummaries.dmarc.pass++ + else if (dmarc === 'fail') returnSummaries.dmarc.fail++ + returnSummaries.dmarc.total++ + + // calculate dmarcPhase summary + if (phase === 'assess') returnSummaries.dmarc_phase.assess++ + else if (phase === 'deploy') returnSummaries.dmarc_phase.deploy++ + else if (phase === 'enforce') returnSummaries.dmarc_phase.enforce++ + else if (phase === 'maintain') returnSummaries.dmarc_phase.maintain++ + returnSummaries.dmarc_phase.total++ + }) + + return { + summaries: returnSummaries, + domainCount: returnSummaries.dmarc_phase.total, + } + } diff --git a/api-js/src/user/loaders/load-user-by-key.js b/api/src/user/loaders/load-user-by-key.js similarity index 90% rename from api-js/src/user/loaders/load-user-by-key.js rename to api/src/user/loaders/load-user-by-key.js index a764137ba7..b85af65a1f 100644 --- a/api-js/src/user/loaders/load-user-by-key.js +++ b/api/src/user/loaders/load-user-by-key.js @@ -1,7 +1,7 @@ import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadUserByKey = ({ query, userKey, i18n }) => +export const loadUserByKey = ({query, userKey, i18n}) => new DataLoader(async (ids) => { let cursor diff --git a/api-js/src/user/loaders/load-user-by-username.js b/api/src/user/loaders/load-user-by-username.js similarity index 90% rename from api-js/src/user/loaders/load-user-by-username.js rename to api/src/user/loaders/load-user-by-username.js index de9697827e..b0c9a5d0a4 100644 --- a/api-js/src/user/loaders/load-user-by-username.js +++ b/api/src/user/loaders/load-user-by-username.js @@ -1,7 +1,7 @@ import DataLoader from 'dataloader' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -export const loadUserByUserName = ({ query, userKey, i18n }) => +export const loadUserByUserName = ({query, userKey, i18n}) => new DataLoader(async (userNames) => { let cursor diff --git a/api/src/user/loaders/load-user-connections-by-user-id.js b/api/src/user/loaders/load-user-connections-by-user-id.js new file mode 100644 index 0000000000..14d0a10755 --- /dev/null +++ b/api/src/user/loaders/load-user-connections-by-user-id.js @@ -0,0 +1,379 @@ +import { aql } from 'arangojs' +import { fromGlobalId, toGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +export const loadUserConnectionsByUserId = + ({ query, userKey, cleanseInput, i18n }) => + async ({ after, before, first, last, orderBy, isSuperAdmin, search }) => { + const userDBId = `users/${userKey}` + + let afterTemplate = aql`` + let afterVar = aql`` + + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(user._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + afterVar = aql`LET afterVar = DOCUMENT(users, ${afterId})` + + let documentField = aql`` + let userField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + documentField = aql`afterVar.userName` + userField = aql`user.userName` + } else if (orderBy.field === 'user-displayName') { + documentField = aql`afterVar.displayName` + userField = aql`user.displayName` + } else if (orderBy.field === 'user-emailValidated') { + documentField = aql`afterVar.emailValidated` + userField = aql`user.emailValidated` + } else if (orderBy.field === 'user-affiliations-totalCount') { + documentField = aql`afterVar.affiliations.totalCount` + userField = aql`user.affiliations.totalCount` + } else if (orderBy.field === 'user-insider') { + documentField = aql`afterVar.insiderUser` + userField = aql`user.insiderUser` + } + + afterTemplate = aql` + FILTER ${userField} ${afterTemplateDirection} ${documentField} + OR (${userField} == ${documentField} + AND TO_NUMBER(user._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(user._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(users, ${beforeId})` + + let documentField = aql`` + let userField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + documentField = aql`beforeVar.userName` + userField = aql`user.userName` + } else if (orderBy.field === 'user-displayName') { + documentField = aql`beforeVar.displayName` + userField = aql`user.displayName` + } else if (orderBy.field === 'user-emailValidated') { + documentField = aql`beforeVar.emailValidated` + userField = aql`user.emailValidated` + } else if (orderBy.field === 'user-affiliations-totalCount') { + documentField = aql`beforeVar.affiliations.totalCount` + userField = aql`user.affiliations.totalCount` + } else if (orderBy.field === 'user-insider') { + documentField = aql`beforeVar.insideUser` + userField = aql`user.insideUser` + } + + beforeTemplate = aql` + FILTER ${userField} ${beforeTemplateDirection} ${documentField} + OR (${userField} == ${documentField} + AND TO_NUMBER(user._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadUserConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`User\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadUserConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`User\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadUserConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` on the \`User\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadUserConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`User\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(user._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(user._key) DESC LIMIT TO_NUMBER(${last})` + } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadUserConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(user._key) > TO_NUMBER(LAST(retrievedUsers)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(user._key) < TO_NUMBER(FIRST(retrievedUsers)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let userField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + userField = aql`user.userName` + hasNextPageDocumentField = aql`LAST(retrievedUsers).userName` + hasPreviousPageDocumentField = aql`FIRST(retrievedUsers).userName` + } else if (orderBy.field === 'user-displayName') { + userField = aql`user.displayName` + hasNextPageDocumentField = aql`LAST(retrievedUsers).displayName` + hasPreviousPageDocumentField = aql`FIRST(retrievedUsers).displayName` + } else if (orderBy.field === 'user-emailValidated') { + userField = aql`user.emailValidated` + hasNextPageDocumentField = aql`LAST(retrievedUsers).emailValidated` + hasPreviousPageDocumentField = aql`FIRST(retrievedUsers).emailValidated` + } else if (orderBy.field === 'user-affiliations-totalCount') { + userField = aql`user.affiliations.totalCount` + hasNextPageDocumentField = aql`LAST(retrievedUsers).affiliations.totalCount` + hasPreviousPageDocumentField = aql`FIRST(retrievedUsers).affiliations.totalCount` + } else if (orderBy.field === 'user-insider') { + userField = aql`user.insideUser` + hasNextPageDocumentField = aql`LAST(retrievedUsers).insideUser` + hasPreviousPageDocumentField = aql`FIRST(retrievedUsers).insideUser` + } + + hasNextPageFilter = aql` + FILTER ${userField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${userField} == ${hasNextPageDocumentField} + AND TO_NUMBER(user._key) > TO_NUMBER(LAST(retrievedUsers)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${userField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${userField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(user._key) < TO_NUMBER(FIRST(retrievedUsers)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + sortByField = aql`user.userName ${orderBy.direction},` + } else if (orderBy.field === 'user-displayName') { + sortByField = aql`user.displayName ${orderBy.direction},` + } else if (orderBy.field === 'user-emailValidated') { + sortByField = aql`user.emailValidated ${orderBy.direction},` + } else if (orderBy.field === 'user-affiliations-totalCount') { + sortByField = aql`user.affiliations.totalCount ${orderBy.direction},` + } else if (orderBy.field === 'user-insider') { + sortByField = aql`user.insideUser ${orderBy.direction},` + } + } + + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } + + let userKeysQuery + if (isSuperAdmin) { + userKeysQuery = aql` + WITH users, userSearch, claims, organizations + LET userKeys = ( + FOR user IN users + RETURN user._key + ) + ` + } else { + userKeysQuery = aql` + WITH affiliations, organizations, users, userSearch, claims + LET userKeys = UNIQUE(FLATTEN( + LET keys = [] + LET orgIds = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + OPTIONS {order: "bfs"} + RETURN e._from + ) + FOR orgId IN orgIds + LET affiliationUserKeys = ( + FOR v, e IN 1..1 OUTBOUND orgId affiliations + OPTIONS {order: "bfs"} + return v._key + ) + RETURN APPEND(keys, affiliationUserKeys) + )) + ` + } + + let userSearchQuery = aql`` + let loopString = aql`FOR user IN users` + let totalCount = aql`LENGTH(userKeys)` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + userSearchQuery = aql` + LET tokenArr = TOKENS(${search}, "text_en") + LET searchedDisplayNamesCount = FLATTEN( + FOR tokenItem in tokenArr + LET token = LOWER(tokenItem) + FOR user IN userSearch + SEARCH ANALYZER( + user.displayName LIKE CONCAT("%", token, "%") + , "text_en") + FILTER user._key IN userKeys + COLLECT currentUser = user WITH COUNT INTO count + RETURN { + 'user': currentUser, + 'count': count + } + ) + LET searchedDisplayNames = searchedDisplayNamesCount[* FILTER CURRENT.count == LENGTH(tokenArr)].user + LET searchedUserNames = ( + FOR user IN users + FILTER LOWER(user.userName) LIKE CONCAT("%", LOWER(${search}), "%") + FILTER user._key IN userKeys + RETURN user + ) + LET searchedUsers = UNIQUE(APPEND(searchedDisplayNames, searchedUserNames)) + ` + loopString = aql`FOR user IN searchedUsers` + totalCount = aql`LENGTH(searchedUsers)` + } + + let requestedUserInfo + try { + requestedUserInfo = await query` + ${userKeysQuery} + + ${userSearchQuery} + + ${afterVar} + ${beforeVar} + + LET retrievedUsers = ( + ${loopString} + FILTER user._key IN userKeys + ${afterTemplate} + ${beforeTemplate} + SORT + ${sortByField} + ${limitTemplate} + RETURN MERGE({ id: user._key, _type: "user" }, user) + ) + + LET hasNextPage = (LENGTH( + ${loopString} + FILTER user._key IN userKeys + ${hasNextPageFilter} + SORT ${sortByField} TO_NUMBER(user._key) ${sortString} LIMIT 1 + RETURN user + ) > 0 ? true : false) + + LET hasPreviousPage = (LENGTH( + ${loopString} + FILTER user._key IN userKeys + ${hasPreviousPageFilter} + SORT ${sortByField} TO_NUMBER(user._key) ${sortString} LIMIT 1 + RETURN user + ) > 0 ? true : false) + + RETURN { + "users": retrievedUsers, + "totalCount": ${totalCount}, + "hasNextPage": hasNextPage, + "hasPreviousPage": hasPreviousPage, + "startKey": FIRST(retrievedUsers)._key, + "endKey": LAST(retrievedUsers)._key + } + ` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query users in loadUserConnectionsByUserId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to query user(s). Please try again.`)) + } + + let usersInfo + try { + usersInfo = await requestedUserInfo.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather users in loadUserConnectionsByUserId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load user(s). Please try again.`)) + } + + if (usersInfo.users.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } + } + + const edges = usersInfo.users.map((user) => { + return { + cursor: toGlobalId('user', user._key), + node: user, + } + }) + + return { + edges, + totalCount: usersInfo.totalCount, + pageInfo: { + hasNextPage: usersInfo.hasNextPage, + hasPreviousPage: usersInfo.hasPreviousPage, + startCursor: toGlobalId('user', usersInfo.startKey), + endCursor: toGlobalId('user', usersInfo.endKey), + }, + } + } diff --git a/api-js/src/user/mutations/__tests__/authenticate.test.js b/api/src/user/mutations/__tests__/authenticate.test.js similarity index 80% rename from api-js/src/user/mutations/__tests__/authenticate.test.js rename to api/src/user/mutations/__tests__/authenticate.test.js index 3222c6c241..94235d3ac7 100644 --- a/api-js/src/user/mutations/__tests__/authenticate.test.js +++ b/api/src/user/mutations/__tests__/authenticate.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import bcrypt from 'bcryptjs' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' @@ -7,18 +8,23 @@ import { v4 as uuidv4 } from 'uuid' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { tokenize, verifyToken } from '../../../auth' import { loadUserByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' +import jwt from 'jsonwebtoken' +import ms from 'ms' const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY, REFRESH_TOKEN_EXPIRY, + AUTH_TOKEN_EXPIRY, + REFRESH_KEY, } = process.env describe('authenticate user account', () => { @@ -46,11 +52,15 @@ describe('authenticate user account', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) afterEach(async () => { @@ -64,7 +74,6 @@ describe('authenticate user account', () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneValidated: false, emailValidated: false, tfaCode: 123456, @@ -91,12 +100,13 @@ describe('authenticate user account', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 123456 authenticateToken: "${token}" } @@ -108,7 +118,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -121,10 +130,10 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, uuidv4, response: mockedResponse, @@ -140,7 +149,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -151,9 +160,8 @@ describe('authenticate user account', () => { id: `${toGlobalId('user', user._key)}`, userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'FRENCH', phoneValidated: false, - emailValidated: false, + emailValidated: true, }, }, }, @@ -178,9 +186,7 @@ describe('authenticate user account', () => { secure: true, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully authenticated their account.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully authenticated their account.`]) }) }) describe('user has rememberMe enabled', () => { @@ -188,7 +194,6 @@ describe('authenticate user account', () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneValidated: false, emailValidated: false, tfaCode: 123456, @@ -215,12 +220,26 @@ describe('authenticate user account', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: '456' }, + secret: String(REFRESH_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken).mockReturnValueOnce(refreshToken) + + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 123456 authenticateToken: "${token}" } @@ -232,7 +251,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -245,16 +263,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, uuidv4, response: mockedResponse, + jwt, auth: { bcrypt, - tokenize: mockTokenize, + tokenize: mockedTokenize, verifyToken: verifyToken({}), }, validators: { @@ -264,20 +283,19 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { authenticate: { result: { - authToken: 'token', + authToken: authToken, user: { id: `${toGlobalId('user', user._key)}`, userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'FRENCH', phoneValidated: false, - emailValidated: false, + emailValidated: true, }, }, }, @@ -295,16 +313,14 @@ describe('authenticate user account', () => { expect(user.tfaCode).toEqual(null) - expect(mockedCookie).toHaveBeenCalledWith('refresh_token', 'token', { + expect(mockedCookie).toHaveBeenCalledWith('refresh_token', refreshToken, { httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, + maxAge: ms(REFRESH_TOKEN_EXPIRY), sameSite: true, secure: true, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully authenticated their account.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully authenticated their account.`]) }) }) }) @@ -331,12 +347,13 @@ describe('authenticate user account', () => { parameters: { userKey: undefined }, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -348,7 +365,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -361,11 +377,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -380,7 +396,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { @@ -394,9 +410,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `Authentication token does not contain the userKey`, - ]) + expect(consoleOutput).toEqual([`Authentication token does not contain the userKey`]) }) }) describe('when userKey is not a field in the token parameters', () => { @@ -405,12 +419,13 @@ describe('authenticate user account', () => { parameters: {}, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -422,7 +437,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -435,11 +449,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -454,7 +468,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { @@ -468,9 +482,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `Authentication token does not contain the userKey`, - ]) + expect(consoleOutput).toEqual([`Authentication token does not contain the userKey`]) }) }) describe('when user cannot be found in database', () => { @@ -479,12 +491,13 @@ describe('authenticate user account', () => { parameters: { userKey: 1 }, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -496,7 +509,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -509,11 +521,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -530,7 +542,7 @@ describe('authenticate user account', () => { }, }, }, - ) + }) const error = { data: { @@ -544,23 +556,42 @@ describe('authenticate user account', () => { } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 1 attempted to authenticate, no account is associated with this id.`, - ]) + expect(consoleOutput).toEqual([`User: 1 attempted to authenticate, no account is associated with this id.`]) }) }) describe('when tfa codes do not match', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) it('returns an error message', async () => { const token = tokenize({ parameters: { userKey: 123 }, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -572,7 +603,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -585,11 +615,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -608,11 +638,9 @@ describe('authenticate user account', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Incorrect TFA code. Please sign in again.'), - ] + const error = [new GraphQLError('Incorrect TFA code. Please sign in again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -628,12 +656,13 @@ describe('authenticate user account', () => { secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 123456 authenticateToken: "${token}" } @@ -645,7 +674,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -658,15 +686,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -689,11 +716,9 @@ describe('authenticate user account', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to authenticate. Please try again.'), - ] + const error = [new GraphQLError('Unable to authenticate. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -710,12 +735,13 @@ describe('authenticate user account', () => { secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 123456 authenticateToken: "${token}" } @@ -727,7 +753,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -740,16 +765,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -772,11 +796,9 @@ describe('authenticate user account', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to authenticate. Please try again.'), - ] + const error = [new GraphQLError('Unable to authenticate. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -807,12 +829,13 @@ describe('authenticate user account', () => { parameters: { userKey: undefined }, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -824,7 +847,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -837,11 +859,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -856,24 +878,21 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { authenticate: { result: { code: 400, - description: - 'La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.', + description: 'La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.', }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `Authentication token does not contain the userKey`, - ]) + expect(consoleOutput).toEqual([`Authentication token does not contain the userKey`]) }) }) describe('when userKey is not a field in the token parameters', () => { @@ -882,12 +901,13 @@ describe('authenticate user account', () => { parameters: {}, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -899,7 +919,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -912,11 +931,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -931,24 +950,21 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { authenticate: { result: { code: 400, - description: - 'La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.', + description: 'La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.', }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `Authentication token does not contain the userKey`, - ]) + expect(consoleOutput).toEqual([`Authentication token does not contain the userKey`]) }) }) describe('when user cannot be found in database', () => { @@ -957,12 +973,13 @@ describe('authenticate user account', () => { parameters: { userKey: 1 }, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -974,7 +991,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -987,11 +1003,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -1008,38 +1024,56 @@ describe('authenticate user account', () => { }, }, }, - ) + }) const error = { data: { authenticate: { result: { code: 400, - description: - "Impossible de s'authentifier. Veuillez réessayer.", + description: "Impossible de s'authentifier. Veuillez réessayer.", }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 1 attempted to authenticate, no account is associated with this id.`, - ]) + expect(consoleOutput).toEqual([`User: 1 attempted to authenticate, no account is associated with this id.`]) }) }) describe('when tfa codes do not match', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) it('returns an error message', async () => { const token = tokenize({ parameters: { userKey: 123 }, secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 654321 authenticateToken: "${token}" } @@ -1051,7 +1085,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1064,11 +1097,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -1087,11 +1120,9 @@ describe('authenticate user account', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Code TFA incorrect. Veuillez vous reconnecter.'), - ] + const error = [new GraphQLError('Code TFA incorrect. Veuillez vous reconnecter.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1107,12 +1138,13 @@ describe('authenticate user account', () => { secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 123456 authenticateToken: "${token}" } @@ -1124,7 +1156,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1137,15 +1168,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -1168,13 +1198,9 @@ describe('authenticate user account', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de s'authentifier. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de s'authentifier. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1191,12 +1217,13 @@ describe('authenticate user account', () => { secret: String(SIGN_IN_KEY), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { authenticate( input: { + sendMethod: EMAIL authenticationCode: 123456 authenticateToken: "${token}" } @@ -1208,7 +1235,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1221,16 +1247,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -1253,13 +1278,9 @@ describe('authenticate user account', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de s'authentifier. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de s'authentifier. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/user/mutations/__tests__/close-account.test.js b/api/src/user/mutations/__tests__/close-account.test.js new file mode 100644 index 0000000000..500f06a53e --- /dev/null +++ b/api/src/user/mutations/__tests__/close-account.test.js @@ -0,0 +1,1548 @@ +import { setupI18n } from '@lingui/core' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { checkSuperAdmin, userRequired } from '../../../auth' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import { cleanseInput } from '../../../validators' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given the closeAccount mutation', () => { + let i18n, query, drop, truncate, schema, collections, transaction, user, org, domain + + const consoleOutput = [] + const mockedConsole = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedConsole + console.warn = mockedConsole + console.error = mockedConsole + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful closing of an account', () => { + beforeEach(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + afterEach(async () => { + await truncate() + await drop() + }) + describe('user is closing their own account', () => { + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, + }) + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, + }) + + const webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) + + const dmarcSummary = await collections.dmarcSummaries.save({ + dmarcSummary: true, + }) + await collections.domainsToDmarcSummaries.save({ + _from: domain._id, + _to: dmarcSummary._id, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('removes the users affiliations', async () => { + await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + + const testAffiliationCursor = await query` + FOR aff IN affiliations + OPTIONS { waitForSync: true } + RETURN aff + ` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Successfully closed account.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Le compte a été fermé avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) + }) + }) + it('closes the users account', async () => { + await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) + + await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` + + const testUserCursor = await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` + const testUser = await testUserCursor.next() + expect(testUser).toEqual(undefined) + }) + }) + describe('super admin is closing another users account', () => { + let superAdmin, superAdminOrg + beforeEach(async () => { + superAdmin = await collections.users.save({ + userName: 'super.admin@istio.actually.exists', + emailValidated: true, + }) + superAdminOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: superAdmin._id, + permission: 'super_admin', + }) + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, + }) + + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, + }) + + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, + }) + + const webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) + + const dmarcSummary = await collections.dmarcSummaries.save({ + dmarcSummary: true, + }) + await collections.domainsToDmarcSummaries.save({ + _from: domain._id, + _to: dmarcSummary._id, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('removes the users affiliations', async () => { + await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + }) + + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` + + const testAffiliationCursor = await query` + FOR aff IN affiliations + OPTIONS { waitForSync: true } + FILTER aff._from != ${superAdminOrg._id} + RETURN aff + ` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Successfully closed account.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Le compte a été fermé avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) + }) + }) + it('closes the users account', async () => { + await graphql({ + schema, + source: ` + mutation { + closeAccountOther(input:{ + userId: "${toGlobalId('user', user._key)}" + }) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: superAdmin._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: superAdmin._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: superAdmin._key, + loadUserByKey: loadUserByKey({ + query, + userKey: superAdmin._key, + i18n, + }), + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ + query, + userKey: superAdmin._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + }) + + await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` + + const testUserCursor = await query` + FOR user IN users + OPTIONS { waitForSync: true } + FILTER user.userName != "super.admin@istio.actually.exists" + RETURN user + ` + const testUser = await testUserCursor.next() + expect(testUser).toEqual(undefined) + }) + }) + }) + + describe('given an unsuccessful closing of an account', () => { + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user attempts to close another users account', () => { + describe('requesting user is not a super admin', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: "Permission error: Unable to close other user's account.", + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, + ]) + }) + }) + describe('requested user is undefined', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: 'Unable to close account of an undefined user.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to close user: 456 account, but requested user is undefined.`, + ]) + }) + }) + }) + describe('trx step error occurs', () => { + describe('when removing the users affiliations', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } + + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('trx step error')), + commit: jest.fn(), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Unable to close account. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, + ]) + }) + }) + describe('when removing the user', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } + + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), + commit: jest.fn(), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Unable to close account. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step error`, + ]) + }) + }) + }) + describe('trx commit error occurs', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } + + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Unable to close account. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to close account: users/123: Error: trx commit error`, + ]) + }) + }) + }) + describe('language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user attempts to close another users account', () => { + describe('requesting user is not a super admin', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.", + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, + ]) + }) + }) + describe('requested user is undefined', () => { + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + validators: { cleanseInput }, + }, + }) + + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: "Impossible de fermer le compte d'un utilisateur non défini.", + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: 123 attempted to close user: 456 account, but requested user is undefined.`, + ]) + }) + }) + }) + describe('trx step error occurs', () => { + describe('when removing the users affiliations', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } + + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('trx step error')), + commit: jest.fn(), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, + ]) + }) + }) + describe('when removing the user', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } + + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), + commit: jest.fn(), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step error`, + ]) + }) + }) + }) + describe('trx commit error occurs', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } + + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), + }) + + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: '123', + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), + }, + }, + validators: { cleanseInput }, + }, + }) + + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to close account: users/123: Error: trx commit error`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/user/mutations/__tests__/complete-tour.test.js b/api/src/user/mutations/__tests__/complete-tour.test.js new file mode 100644 index 0000000000..9d20c99d69 --- /dev/null +++ b/api/src/user/mutations/__tests__/complete-tour.test.js @@ -0,0 +1,282 @@ +import { dbNameFromFile } from 'arango-tools' +import { createUserContextGenerator, ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' +import { createI18n } from '../../../create-i18n' +import { toGlobalId } from 'graphql-relay/index' + +const { DB_PASS: rootPass, DB_URL: url, AUTHENTICATED_KEY, HASHING_SALT } = process.env + +const schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), +}) +const consoleOutput = [] +const mockedInfo = (output) => consoleOutput.push(output) +const mockedWarn = (output) => consoleOutput.push(output) +const mockedError = (output) => consoleOutput.push(output) +console.info = mockedInfo +console.warn = mockedWarn +console.error = mockedError + +const i18n = createI18n('en') + +let db, query, drop, truncate, collections, transaction, createUserContext, normalUser, normalUserContext + +describe('complete tour mutation', () => { + beforeAll(async () => { + ;({ db, query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + + createUserContext = createUserContextGenerator({ + db, + query, + transaction, + collectionNames, + i18n, + secret: AUTHENTICATED_KEY, + salt: HASHING_SALT, + }) + }) + + beforeEach(async () => { + normalUser = ( + await collections.users.save( + { + _key: 'normaluser', + userName: 'normaluser@test.gc.ca', + emailValidated: true, + }, + { returnNew: true }, + ) + ).new + normalUserContext = await createUserContext({ userKey: normalUser._key }) + }) + + afterEach(async () => { + consoleOutput.length = 0 + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + it('adds completed tour on successful mutation', async () => { + const currentUserState = await (await query`RETURN DOCUMENT(users, ${normalUser._key})`).next() + + expect(currentUserState?.completedTours).toBeUndefined() + + const tourToCompleteOne = 'tour1' + + const response = await graphql({ + schema, + source: ` + mutation { + completeTour(input: { tourId: "${tourToCompleteOne}" }) { + result { + ... on CompleteTourResult { + status + user { + id + completedTours { + tourId + completedAt + } + } + } + ... on CompleteTourError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const tourOneCompleteTime = response?.data?.completeTour?.result?.user?.completedTours?.[0]?.completedAt + expect(!!tourOneCompleteTime).not.toBeFalsy() + + const expectedResponse = { + data: { + completeTour: { + result: { + status: 'Tour completion confirmed successfully', + user: { + id: toGlobalId('user', normalUser._key), + completedTours: [ + { + tourId: tourToCompleteOne, + completedAt: tourOneCompleteTime, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + const tourToCompleteTwo = 'tour2' + + const responseTwo = await graphql({ + schema, + source: ` + mutation { + completeTour(input: { tourId: "${tourToCompleteTwo}" }) { + result { + ... on CompleteTourResult { + status + user { + id + completedTours { + tourId + completedAt + } + } + } + ... on CompleteTourError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const tourTwoCompleteTime = responseTwo?.data?.completeTour?.result?.user?.completedTours?.[1]?.completedAt + expect(!!tourTwoCompleteTime).not.toBeFalsy() + + const expectedResponseTwo = { + data: { + completeTour: { + result: { + status: 'Tour completion confirmed successfully', + user: { + id: toGlobalId('user', normalUser._key), + completedTours: [ + { + tourId: tourToCompleteOne, + completedAt: tourOneCompleteTime, + }, + { + tourId: tourToCompleteTwo, + completedAt: tourTwoCompleteTime, + }, + ], + }, + }, + }, + }, + } + + expect(responseTwo).toEqual(expectedResponseTwo) + }) + it('updates timestamp for re-completed tour', async () => { + const tourToComplete = 'tour1' + await query`UPDATE { _key: ${normalUser._key}} WITH { completedTours: [{tourId: ${tourToComplete}, completedAt: DATE_ISO8601(DATE_NOW())}]} IN users` + const currentUserState = await (await query`RETURN DOCUMENT(users, ${normalUser._key})`).next() + + expect(currentUserState?.completedTours).toHaveLength(1) + + const originalCompletedAt = currentUserState?.completedTours[0].completedAt + expect(!!originalCompletedAt).not.toBeFalsy() + + const _response = await graphql({ + schema, + source: ` + mutation { + completeTour(input: { tourId: "${tourToComplete}" }) { + result { + ... on CompleteTourResult { + status + user { + id + completedTours { + tourId + completedAt + } + } + } + ... on CompleteTourError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const newUserState = await (await query`RETURN DOCUMENT(users, ${normalUser._key})`).next() + expect(newUserState?.completedTours).toHaveLength(1) + expect(new Date(newUserState.completedTours[0].completedAt) > new Date(originalCompletedAt)).toBe(true) + }) + it('throws an error when tourId is empty after input cleansing', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + completeTour(input: { tourId: " " }) { + result { + ... on CompleteTourResult { + status + user { + id + completedTours { + tourId + completedAt + } + } + } + ... on CompleteTourError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const expectedResponse = { + data: { + completeTour: { + result: { + code: 400, + description: 'Unable to confirm completion of the tour. Please try again.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toHaveLength(1) + expect(consoleOutput[0]).toEqual( + `User: ${normalUser._key} did not provide a tour id when attempting to confirm completion of the tour.`, + ) + }) +}) diff --git a/api/src/user/mutations/__tests__/dismiss-message.test.js b/api/src/user/mutations/__tests__/dismiss-message.test.js new file mode 100644 index 0000000000..4a17794e82 --- /dev/null +++ b/api/src/user/mutations/__tests__/dismiss-message.test.js @@ -0,0 +1,282 @@ +import { dbNameFromFile } from 'arango-tools' +import { createUserContextGenerator, ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema } from 'graphql' + +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' +import { createI18n } from '../../../create-i18n' +import { toGlobalId } from 'graphql-relay/index' + +const { DB_PASS: rootPass, DB_URL: url, AUTHENTICATED_KEY, HASHING_SALT } = process.env + +const schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), +}) +const consoleOutput = [] +const mockedInfo = (output) => consoleOutput.push(output) +const mockedWarn = (output) => consoleOutput.push(output) +const mockedError = (output) => consoleOutput.push(output) +console.info = mockedInfo +console.warn = mockedWarn +console.error = mockedError + +const i18n = createI18n('en') + +let db, query, drop, truncate, collections, transaction, createUserContext, normalUser, normalUserContext + +describe('dismiss message mutation', () => { + beforeAll(async () => { + ;({ db, query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + schema: dbschema, + })) + + createUserContext = createUserContextGenerator({ + db, + query, + transaction, + collectionNames, + i18n, + secret: AUTHENTICATED_KEY, + salt: HASHING_SALT, + }) + }) + + beforeEach(async () => { + normalUser = ( + await collections.users.save( + { + _key: 'normaluser', + userName: 'normaluser@test.gc.ca', + emailValidated: true, + }, + { returnNew: true }, + ) + ).new + normalUserContext = await createUserContext({ userKey: normalUser._key }) + }) + + afterEach(async () => { + consoleOutput.length = 0 + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + it('adds dismissed message on successful mutation', async () => { + const currentUserState = await (await query`RETURN DOCUMENT(users, ${normalUser._key})`).next() + + expect(currentUserState?.dismissedMessages).toBeUndefined() + + const messageToIgnoreOne = 'message1' + + const response = await graphql({ + schema, + source: ` + mutation { + dismissMessage(input: { messageId: "${messageToIgnoreOne}" }) { + result { + ... on DismissMessageResult { + status + user { + id + dismissedMessages { + messageId + dismissedAt + } + } + } + ... on DismissMessageError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const messageOneDismissTime = response?.data?.dismissMessage?.result?.user?.dismissedMessages?.[0]?.dismissedAt + expect(!!messageOneDismissTime).not.toBeFalsy() + + const expectedResponse = { + data: { + dismissMessage: { + result: { + status: 'Message dismissed successfully', + user: { + id: toGlobalId('user', normalUser._key), + dismissedMessages: [ + { + messageId: messageToIgnoreOne, + dismissedAt: messageOneDismissTime, + }, + ], + }, + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + + const messageToIgnoreTwo = 'message2' + + const responseTwo = await graphql({ + schema, + source: ` + mutation { + dismissMessage(input: { messageId: "${messageToIgnoreTwo}" }) { + result { + ... on DismissMessageResult { + status + user { + id + dismissedMessages { + messageId + dismissedAt + } + } + } + ... on DismissMessageError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const messageTwoDismissTime = responseTwo?.data?.dismissMessage?.result?.user?.dismissedMessages?.[1]?.dismissedAt + expect(!!messageTwoDismissTime).not.toBeFalsy() + + const expectedResponseTwo = { + data: { + dismissMessage: { + result: { + status: 'Message dismissed successfully', + user: { + id: toGlobalId('user', normalUser._key), + dismissedMessages: [ + { + messageId: messageToIgnoreOne, + dismissedAt: messageOneDismissTime, + }, + { + messageId: messageToIgnoreTwo, + dismissedAt: messageTwoDismissTime, + }, + ], + }, + }, + }, + }, + } + + expect(responseTwo).toEqual(expectedResponseTwo) + }) + it('updates timestamp for re-ignored message', async () => { + const messageToIgnore = 'message1' + await query`UPDATE { _key: ${normalUser._key}} WITH { dismissedMessages: [{messageId: ${messageToIgnore}, dismissedAt: DATE_ISO8601(DATE_NOW())}]} IN users` + const currentUserState = await (await query`RETURN DOCUMENT(users, ${normalUser._key})`).next() + + expect(currentUserState?.dismissedMessages).toHaveLength(1) + + const originalDismissedAt = currentUserState?.dismissedMessages[0].dismissedAt + expect(!!originalDismissedAt).not.toBeFalsy() + + const _response = await graphql({ + schema, + source: ` + mutation { + dismissMessage(input: { messageId: "${messageToIgnore}" }) { + result { + ... on DismissMessageResult { + status + user { + id + dismissedMessages { + messageId + dismissedAt + } + } + } + ... on DismissMessageError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const newUserState = await (await query`RETURN DOCUMENT(users, ${normalUser._key})`).next() + expect(newUserState?.dismissedMessages).toHaveLength(1) + expect(new Date(newUserState.dismissedMessages[0].dismissedAt) > new Date(originalDismissedAt)).toBe(true) + }) + it('throws an error when messageId is empty after input cleansing', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + dismissMessage(input: { messageId: " " }) { + result { + ... on DismissMessageResult { + status + user { + id + dismissedMessages { + messageId + dismissedAt + } + } + } + ... on DismissMessageError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: normalUserContext, + }) + + const expectedResponse = { + data: { + dismissMessage: { + result: { + code: 400, + description: 'Unable to dismiss message. Please try again.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toHaveLength(1) + expect(consoleOutput[0]).toEqual( + `User: ${normalUser._key} did not provide a message id when attempting to dismiss a message.`, + ) + }) +}) diff --git a/api-js/src/user/mutations/__tests__/refresh-tokens.test.js b/api/src/user/mutations/__tests__/refresh-tokens.test.js similarity index 85% rename from api-js/src/user/mutations/__tests__/refresh-tokens.test.js rename to api/src/user/mutations/__tests__/refresh-tokens.test.js index b2c03c788a..c726fdb358 100644 --- a/api-js/src/user/mutations/__tests__/refresh-tokens.test.js +++ b/api/src/user/mutations/__tests__/refresh-tokens.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' import { v4 as uuidv4 } from 'uuid' @@ -6,18 +7,22 @@ import jwt from 'jsonwebtoken' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadUserByKey } from '../../loaders' import { tokenize } from '../../../auth' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' +import ms from 'ms' const { DB_PASS: rootPass, DB_URL: url, REFRESH_KEY, REFRESH_TOKEN_EXPIRY, + AUTH_TOKEN_EXPIRY, + SIGN_IN_KEY, } = process.env describe('refresh users tokens', () => { @@ -44,11 +49,15 @@ describe('refresh users tokens', () => { describe('given a successful refresh', () => { beforeAll(async () => { ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) afterEach(async () => { @@ -62,7 +71,6 @@ describe('refresh users tokens', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', phoneValidated: false, emailValidated: false, tfaCode: null, @@ -93,9 +101,9 @@ describe('refresh users tokens', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -113,10 +121,10 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -133,7 +141,7 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -155,9 +163,7 @@ describe('refresh users tokens', () => { sameSite: true, secure: true, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully refreshed their tokens.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully refreshed their tokens.`]) }) }) describe('user has rememberMe enabled', () => { @@ -165,7 +171,6 @@ describe('refresh users tokens', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', phoneValidated: false, emailValidated: false, tfaCode: null, @@ -177,13 +182,6 @@ describe('refresh users tokens', () => { }) }) it('returns a new auth, and refresh token', async () => { - const refreshToken = tokenize({ - parameters: { userKey: user._key, uuid: '1234' }, - expPeriod: 168, - secret: String(REFRESH_KEY), - }) - const mockedRequest = { cookies: { refresh_token: refreshToken } } - const mockedFormat = jest .fn() .mockReturnValueOnce('2021-06-30T12:00:00') @@ -196,9 +194,24 @@ describe('refresh users tokens', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: '1234' }, + secret: String(REFRESH_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken).mockReturnValueOnce(refreshToken) + + const mockedRequest = { cookies: { refresh_token: refreshToken } } + + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -216,10 +229,10 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -227,7 +240,7 @@ describe('refresh users tokens', () => { request: mockedRequest, response: mockedResponse, auth: { - tokenize: jest.fn().mockReturnValue('token'), + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -236,13 +249,13 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { - authToken: 'token', + authToken: authToken, user: { displayName: 'Test Account', }, @@ -252,15 +265,13 @@ describe('refresh users tokens', () => { } expect(response).toEqual(expectedResult) - expect(mockedCookie).toHaveBeenCalledWith('refresh_token', 'token', { + expect(mockedCookie).toHaveBeenCalledWith('refresh_token', refreshToken, { httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, + maxAge: ms(REFRESH_TOKEN_EXPIRY), sameSite: true, secure: true, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully refreshed their tokens.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully refreshed their tokens.`]) }) }) }) @@ -294,9 +305,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -314,11 +325,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -334,7 +345,7 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -348,9 +359,7 @@ describe('refresh users tokens', () => { } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User attempted to refresh tokens without refresh_token set.`, - ]) + expect(consoleOutput).toEqual([`User attempted to refresh tokens without refresh_token set.`]) }) }) describe('refresh token is invalid', () => { @@ -370,9 +379,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -390,11 +399,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -410,7 +419,7 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -446,9 +455,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -466,11 +475,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -488,7 +497,7 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { @@ -502,9 +511,7 @@ describe('refresh users tokens', () => { } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 1234 attempted to refresh tokens with an invalid user id.`, - ]) + expect(consoleOutput).toEqual([`User: 1234 attempted to refresh tokens with an invalid user id.`]) }) }) describe('if the token is expired', () => { @@ -524,9 +531,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(true), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -544,11 +551,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -570,7 +577,7 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { @@ -584,9 +591,7 @@ describe('refresh users tokens', () => { } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to refresh tokens with an expired uuid.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to refresh tokens with an expired uuid.`]) }) }) describe('if the uuids do not match', () => { @@ -606,9 +611,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -626,11 +631,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -652,7 +657,7 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { @@ -666,9 +671,7 @@ describe('refresh users tokens', () => { } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to refresh tokens with non matching uuids.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to refresh tokens with non matching uuids.`]) }) }) }) @@ -676,9 +679,8 @@ describe('refresh users tokens', () => { describe('when upserting new refreshId', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }) const refreshToken = tokenize({ @@ -696,9 +698,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -716,11 +718,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: mockedTransaction, uuidv4, jwt, @@ -743,11 +745,9 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to refresh tokens, please sign in.'), - ] + const error = [new GraphQLError('Unable to refresh tokens, please sign in.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -761,9 +761,8 @@ describe('refresh users tokens', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }) const refreshToken = tokenize({ @@ -782,9 +781,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -802,11 +801,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: mockedTransaction, uuidv4, jwt, @@ -829,11 +828,9 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to refresh tokens, please sign in.'), - ] + const error = [new GraphQLError('Unable to refresh tokens, please sign in.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -872,9 +869,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -892,11 +889,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -912,24 +909,21 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { code: 400, - description: - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', + description: 'Impossible de rafraîchir les jetons, veuillez vous connecter.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User attempted to refresh tokens without refresh_token set.`, - ]) + expect(consoleOutput).toEqual([`User attempted to refresh tokens without refresh_token set.`]) }) }) describe('refresh token is invalid', () => { @@ -949,9 +943,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -969,11 +963,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -989,15 +983,14 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { code: 400, - description: - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', + description: 'Impossible de rafraîchir les jetons, veuillez vous connecter.', }, }, }, @@ -1026,9 +1019,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -1046,11 +1039,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -1068,24 +1061,21 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { code: 400, - description: - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', + description: 'Impossible de rafraîchir les jetons, veuillez vous connecter.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 1234 attempted to refresh tokens with an invalid user id.`, - ]) + expect(consoleOutput).toEqual([`User: 1234 attempted to refresh tokens with an invalid user id.`]) }) }) describe('if the token is expired', () => { @@ -1105,9 +1095,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(true), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -1125,11 +1115,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -1151,24 +1141,21 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { code: 400, - description: - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', + description: 'Impossible de rafraîchir les jetons, veuillez vous connecter.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to refresh tokens with an expired uuid.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to refresh tokens with an expired uuid.`]) }) }) describe('if the uuids do not match', () => { @@ -1188,9 +1175,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -1208,11 +1195,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, jwt, @@ -1234,24 +1221,21 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { code: 400, - description: - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', + description: 'Impossible de rafraîchir les jetons, veuillez vous connecter.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to refresh tokens with non matching uuids.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to refresh tokens with non matching uuids.`]) }) }) }) @@ -1259,9 +1243,8 @@ describe('refresh users tokens', () => { describe('when upserting new refreshId', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }) const refreshToken = tokenize({ @@ -1279,9 +1262,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -1299,11 +1282,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: mockedTransaction, uuidv4, jwt, @@ -1326,13 +1309,9 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', - ), - ] + const error = [new GraphQLError('Impossible de rafraîchir les jetons, veuillez vous connecter.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1346,9 +1325,8 @@ describe('refresh users tokens', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }) const refreshToken = tokenize({ @@ -1367,9 +1345,9 @@ describe('refresh users tokens', () => { isAfter: jest.fn().mockReturnValue(false), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { refreshTokens(input: {}) { result { @@ -1387,11 +1365,11 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: mockedTransaction, uuidv4, jwt, @@ -1414,13 +1392,9 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de rafraîchir les jetons, veuillez vous connecter.', - ), - ] + const error = [new GraphQLError('Impossible de rafraîchir les jetons, veuillez vous connecter.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api-js/src/user/mutations/__tests__/remove-phone-number.test.js b/api/src/user/mutations/__tests__/remove-phone-number.test.js similarity index 82% rename from api-js/src/user/mutations/__tests__/remove-phone-number.test.js rename to api/src/user/mutations/__tests__/remove-phone-number.test.js index 6993813b0c..ab123372a8 100644 --- a/api-js/src/user/mutations/__tests__/remove-phone-number.test.js +++ b/api/src/user/mutations/__tests__/remove-phone-number.test.js @@ -1,14 +1,16 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { userRequired } from '../../../auth' import { loadUserByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -38,11 +40,15 @@ describe('testing the removePhoneNumber mutation', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) afterEach(async () => { @@ -81,9 +87,9 @@ describe('testing the removePhoneNumber mutation', () => { }) }) it('executes mutation successfully', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -98,10 +104,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -111,7 +117,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -124,14 +130,12 @@ describe('testing the removePhoneNumber mutation', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed their phone number.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully removed their phone number.`]) }) it('sets phoneDetails to null', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -146,10 +150,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -159,18 +163,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneDetails).toEqual(null) }) it('sets phoneValidated to false', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -185,10 +187,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -198,18 +200,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneValidated).toEqual(false) }) it('changes tfaSendMethod to email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -224,10 +224,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -237,11 +237,9 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.tfaSendMethod).toEqual('email') }) @@ -261,9 +259,9 @@ describe('testing the removePhoneNumber mutation', () => { }) }) it('executes mutation successfully', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -278,10 +276,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -291,7 +289,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -304,14 +302,12 @@ describe('testing the removePhoneNumber mutation', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed their phone number.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully removed their phone number.`]) }) it('sets phoneDetails to null', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -326,10 +322,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -339,18 +335,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneDetails).toEqual(null) }) it('sets phoneValidated to false', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -365,10 +359,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -378,18 +372,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneValidated).toEqual(false) }) it('changes tfaSendMethod to email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -404,10 +396,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -417,11 +409,9 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.tfaSendMethod).toEqual('none') }) @@ -457,9 +447,9 @@ describe('testing the removePhoneNumber mutation', () => { }) }) it('executes mutation successfully', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -474,10 +464,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -487,7 +477,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -500,14 +490,12 @@ describe('testing the removePhoneNumber mutation', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed their phone number.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully removed their phone number.`]) }) it('sets phoneDetails to null', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -522,10 +510,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -535,18 +523,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneDetails).toEqual(null) }) it('sets phoneValidated to false', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -561,10 +547,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -574,18 +560,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneValidated).toEqual(false) }) it('changes tfaSendMethod to email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -600,10 +584,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -613,11 +597,9 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.tfaSendMethod).toEqual('email') }) @@ -637,9 +619,9 @@ describe('testing the removePhoneNumber mutation', () => { }) }) it('executes mutation successfully', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -654,10 +636,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -667,7 +649,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -680,14 +662,12 @@ describe('testing the removePhoneNumber mutation', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully removed their phone number.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully removed their phone number.`]) }) it('sets phoneDetails to null', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -702,10 +682,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -715,18 +695,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneDetails).toEqual(null) }) it('sets phoneValidated to false', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -741,10 +719,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -754,18 +732,16 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.phoneValidated).toEqual(false) }) it('changes tfaSendMethod to email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -780,10 +756,10 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction, auth: { @@ -793,11 +769,9 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) - user = await loadUserByKey({ query, userKey: user._key }).load( - user._key, - ) + user = await loadUserByKey({ query, userKey: user._key }).load(user._key) expect(user.tfaSendMethod).toEqual('none') }) @@ -825,16 +799,13 @@ describe('testing the removePhoneNumber mutation', () => { describe('when running upsert', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue( - new Error('transaction step error occurred.'), - ), + step: jest.fn().mockRejectedValue(new Error('transaction step error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -849,23 +820,19 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction: mockedTransaction, auth: { userRequired: jest.fn().mockReturnValue({ _key: 123 }), }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to remove phone number. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove phone number. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -879,16 +846,13 @@ describe('testing the removePhoneNumber mutation', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue( - new Error('transaction step error occurred.'), - ), + commit: jest.fn().mockRejectedValue(new Error('transaction step error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -903,23 +867,19 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction: mockedTransaction, auth: { userRequired: jest.fn().mockReturnValue({ _key: 123 }), }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to remove phone number. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove phone number. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -948,16 +908,13 @@ describe('testing the removePhoneNumber mutation', () => { describe('when running upsert', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue( - new Error('transaction step error occurred.'), - ), + step: jest.fn().mockRejectedValue(new Error('transaction step error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -972,23 +929,19 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction: mockedTransaction, auth: { userRequired: jest.fn().mockReturnValue({ _key: 123 }), }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le numéro de téléphone. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le numéro de téléphone. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1002,16 +955,13 @@ describe('testing the removePhoneNumber mutation', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue( - new Error('transaction step error occurred.'), - ), + commit: jest.fn().mockRejectedValue(new Error('transaction step error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removePhoneNumber(input: {}) { result { @@ -1026,23 +976,19 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - collections, + collections: collectionNames, query, transaction: mockedTransaction, auth: { userRequired: jest.fn().mockReturnValue({ _key: 123 }), }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de supprimer le numéro de téléphone. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de supprimer le numéro de téléphone. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api-js/src/user/mutations/__tests__/reset-password.test.js b/api/src/user/mutations/__tests__/reset-password.test.js similarity index 80% rename from api-js/src/user/mutations/__tests__/reset-password.test.js rename to api/src/user/mutations/__tests__/reset-password.test.js index b7b3a23f40..366aa1b79b 100644 --- a/api-js/src/user/mutations/__tests__/reset-password.test.js +++ b/api/src/user/mutations/__tests__/reset-password.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import bcrypt from 'bcryptjs' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' @@ -7,19 +8,20 @@ import jwt from 'jsonwebtoken' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { tokenize, verifyToken } from '../../../auth' import { loadUserByUserName, loadUserByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env const mockNotify = jest.fn() describe('reset users password', () => { - let query, drop, truncate, schema, i18n, collections, transaction + let query, drop, truncate, schema, i18n, transaction const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -41,18 +43,22 @@ describe('reset users password', () => { describe('given a successful reset', () => { beforeAll(async () => { // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + ;({ query, drop, truncate, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -60,23 +66,21 @@ describe('reset users password', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - user { - id - } + ... on TFASignInResult { + authenticateToken + sendMethod } } } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, jwt, uuidv4, @@ -96,9 +100,10 @@ describe('reset users password', () => { request: { protocol: 'https', get: (text) => text, + ip: '127.0.0.1', }, }, - ) + }) }) afterEach(async () => { await truncate() @@ -133,9 +138,9 @@ describe('reset users password', () => { parameters: { userKey: user._key, currentPassword: user.password }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -156,11 +161,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -175,7 +180,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -188,16 +193,14 @@ describe('reset users password', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully reset their password.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully reset their password.`]) consoleOutput.length = 0 const mockedResponse = { cookie: jest.fn() } - const testSignIn = await graphql( + const testSignIn = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -217,15 +220,16 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, jwt, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: jest.fn().mockReturnValue('token'), @@ -240,22 +244,21 @@ describe('reset users password', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedTestSignIn = { data: { signIn: { result: { - authToken: 'token', + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } expect(testSignIn).toEqual(expectedTestSignIn) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) it('resets failed login attempts', async () => { const userCursor = await query` @@ -270,9 +273,9 @@ describe('reset users password', () => { parameters: { userKey: user._key, currentPassword: user.password }, }) - await graphql( + await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -293,12 +296,13 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -312,7 +316,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const checkCursor = await query` FOR user IN users @@ -351,9 +355,9 @@ describe('reset users password', () => { parameters: { userKey: user._key, currentPassword: user.password }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -374,11 +378,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -393,7 +397,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -406,16 +410,14 @@ describe('reset users password', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully reset their password.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully reset their password.`]) consoleOutput.length = 0 const mockedResponse = { cookie: jest.fn() } - const testSignIn = await graphql( + const testSignIn = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -435,15 +437,16 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, response: mockedResponse, jwt, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: jest.fn().mockReturnValue('token'), @@ -458,22 +461,21 @@ describe('reset users password', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedTestSignIn = { data: { signIn: { result: { - authToken: 'token', + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } expect(testSignIn).toEqual(expectedTestSignIn) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) it('resets failed login attempts', async () => { const userCursor = await query` @@ -488,9 +490,9 @@ describe('reset users password', () => { parameters: { userKey: user._key, currentPassword: user.password }, }) - await graphql( + await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -511,11 +513,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -530,7 +532,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const checkCursor = await query` FOR user IN users @@ -565,9 +567,9 @@ describe('reset users password', () => { parameters: {}, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -588,11 +590,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -608,15 +610,14 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { resetPassword: { result: { code: 400, - description: - 'Incorrect token value. Please request a new email.', + description: 'Incorrect token value. Please request a new email.', }, }, }, @@ -634,9 +635,9 @@ describe('reset users password', () => { parameters: { userKey: undefined }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -657,11 +658,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -677,15 +678,14 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { resetPassword: { result: { code: 400, - description: - 'Incorrect token value. Please request a new email.', + description: 'Incorrect token value. Please request a new email.', }, }, }, @@ -703,9 +703,9 @@ describe('reset users password', () => { parameters: { userKey: 1, currentPassword: 'secretPassword' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -726,11 +726,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -746,7 +746,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -765,90 +765,15 @@ describe('reset users password', () => { ]) }) }) - describe('password in token does not match users current password', () => { - it('returns an error message', async () => { - const resetToken = tokenize({ - parameters: { - userKey: 123, - currentPassword: 'secretPassword', - }, - }) - - const response = await graphql( - schema, - ` - mutation { - resetPassword ( - input: { - password: "newpassword123" - confirmPassword: "newpassword123" - resetToken: "${resetToken}" - } - ) { - result { - ... on ResetPasswordError { - code - description - } - ... on ResetPasswordResult { - status - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - auth: { - bcrypt, - tokenize, - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - password: 'notTheRightPassword', - }), - }, - }, - }, - ) - - const error = { - data: { - resetPassword: { - result: { - code: 400, - description: - 'Unable to reset password. Please request a new email.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to reset password, however the current password does not match the current hashed password in the db.`, - ]) - }) - }) describe('new passwords do not match', () => { it('returns an error message', async () => { const resetToken = tokenize({ parameters: { userKey: 123, currentPassword: 'currentPassword' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -869,11 +794,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -892,7 +817,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -917,9 +842,9 @@ describe('reset users password', () => { parameters: { userKey: 123, currentPassword: 'currentPassword' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -940,11 +865,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -963,7 +888,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -992,9 +917,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1015,15 +940,14 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), auth: { bcrypt, @@ -1042,11 +966,9 @@ describe('reset users password', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to reset password. Please try again.'), - ] + const error = [new GraphQLError('Unable to reset password. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1065,9 +987,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1088,16 +1010,15 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), auth: { bcrypt, @@ -1116,11 +1037,9 @@ describe('reset users password', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to reset password. Please try again.'), - ] + const error = [new GraphQLError('Unable to reset password. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1151,9 +1070,9 @@ describe('reset users password', () => { parameters: {}, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1174,11 +1093,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1194,15 +1113,14 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { resetPassword: { result: { code: 400, - description: - 'La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.', + description: 'La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.', }, }, }, @@ -1220,9 +1138,9 @@ describe('reset users password', () => { parameters: { userKey: undefined }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1243,11 +1161,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1263,15 +1181,14 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { resetPassword: { result: { code: 400, - description: - 'La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.', + description: 'La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.', }, }, }, @@ -1289,9 +1206,9 @@ describe('reset users password', () => { parameters: { userKey: 1, currentPassword: 'secretPassword' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1312,11 +1229,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1332,90 +1249,14 @@ describe('reset users password', () => { }, }, }, - ) - - const error = { - data: { - resetPassword: { - result: { - code: 400, - description: - 'Impossible de réinitialiser le mot de passe. Veuillez réessayer.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `A user attempted to reset the password for 1, however there is no associated account.`, - ]) - }) - }) - describe('password in token does not match users current password', () => { - it('returns an error message', async () => { - const resetToken = tokenize({ - parameters: { - userKey: 123, - currentPassword: 'secretPassword', - }, }) - const response = await graphql( - schema, - ` - mutation { - resetPassword ( - input: { - password: "newpassword123" - confirmPassword: "newpassword123" - resetToken: "${resetToken}" - } - ) { - result { - ... on ResetPasswordError { - code - description - } - ... on ResetPasswordResult { - status - } - } - } - } - `, - null, - { - i18n, - query, - collections, - transaction, - auth: { - bcrypt, - tokenize, - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - password: 'notTheRightPassword', - }), - }, - }, - }, - ) - const error = { data: { resetPassword: { result: { code: 400, - description: - 'Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.', + description: 'Impossible de réinitialiser le mot de passe. Veuillez réessayer.', }, }, }, @@ -1423,7 +1264,7 @@ describe('reset users password', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to reset password, however the current password does not match the current hashed password in the db.`, + `A user attempted to reset the password for 1, however there is no associated account.`, ]) }) }) @@ -1433,9 +1274,9 @@ describe('reset users password', () => { parameters: { userKey: 123, currentPassword: 'currentPassword' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1456,11 +1297,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1479,15 +1320,14 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { resetPassword: { result: { code: 400, - description: - 'Les nouveaux mots de passe ne correspondent pas.', + description: 'Les nouveaux mots de passe ne correspondent pas.', }, }, }, @@ -1505,9 +1345,9 @@ describe('reset users password', () => { parameters: { userKey: 123, currentPassword: 'currentPassword' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1528,11 +1368,11 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1551,7 +1391,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -1580,9 +1420,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1603,15 +1443,14 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), auth: { bcrypt, @@ -1630,13 +1469,9 @@ describe('reset users password', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de réinitialiser le mot de passe. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de réinitialiser le mot de passe. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1655,9 +1490,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1678,16 +1513,15 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), auth: { bcrypt, @@ -1706,13 +1540,9 @@ describe('reset users password', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de réinitialiser le mot de passe. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de réinitialiser le mot de passe. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api-js/src/user/mutations/__tests__/send-password-reset.test.js b/api/src/user/mutations/__tests__/send-password-reset.test.js similarity index 86% rename from api-js/src/user/mutations/__tests__/send-password-reset.test.js rename to api/src/user/mutations/__tests__/send-password-reset.test.js index d97a680dcf..9fe11e1184 100644 --- a/api-js/src/user/mutations/__tests__/send-password-reset.test.js +++ b/api/src/user/mutations/__tests__/send-password-reset.test.js @@ -1,22 +1,22 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import bcrypt from 'bcryptjs' import { graphql, GraphQLSchema } from 'graphql' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadUserByUserName } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env const mockNotify = jest.fn() const tokenize = jest.fn().mockReturnValue('token') describe('user send password reset email', () => { - let query, drop, truncate, collections, schema, request, i18n const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -42,11 +42,15 @@ describe('user send password reset email', () => { describe('successfully sends password reset email', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) afterEach(async () => { @@ -75,15 +79,14 @@ describe('user send password reset email', () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }) }) it('returns status text', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { sendPasswordResetLink( input: { userName: "test.account@istio.actually.exists" } @@ -92,8 +95,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -111,13 +114,12 @@ describe('user send password reset email', () => { sendPasswordResetEmail: mockNotify, }, }, - ) + }) const expectedResult = { data: { sendPasswordResetLink: { - status: - 'If an account with this username is found, a password reset link will be found in your inbox.', + status: 'If an account with this username is found, a password reset link will be found in your inbox.', }, }, } @@ -131,18 +133,14 @@ describe('user send password reset email', () => { const token = tokenize({ parameters: { userKey: user._key, currentPassword: user.password }, }) - const resetUrl = `https://${request.get( - 'host', - )}/reset-password/${token}` + const resetUrl = `https://${request.get('host')}/reset-password/${token}` expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ user, resetUrl, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully sent a password reset email.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully sent a password reset email.`]) }) }) }) @@ -165,15 +163,14 @@ describe('user send password reset email', () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) }) it('returns status text', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { sendPasswordResetLink( input: { userName: "test.account@istio.actually.exists" } @@ -182,8 +179,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -201,7 +198,7 @@ describe('user send password reset email', () => { sendPasswordResetEmail: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -221,18 +218,14 @@ describe('user send password reset email', () => { const token = tokenize({ parameters: { userKey: user._key, currentPassword: user.password }, }) - const resetUrl = `https://${request.get( - 'host', - )}/reset-password/${token}` + const resetUrl = `https://${request.get('host')}/reset-password/${token}` expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ user, resetUrl, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully sent a password reset email.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully sent a password reset email.`]) }) }) }) @@ -253,9 +246,9 @@ describe('user send password reset email', () => { }) }) it('returns status text', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { sendPasswordResetLink( input: { @@ -266,8 +259,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -287,13 +280,12 @@ describe('user send password reset email', () => { sendPasswordResetEmail: mockNotify, }, }, - ) + }) const expectedResult = { data: { sendPasswordResetLink: { - status: - 'If an account with this username is found, a password reset link will be found in your inbox.', + status: 'If an account with this username is found, a password reset link will be found in your inbox.', }, }, } @@ -321,9 +313,9 @@ describe('user send password reset email', () => { }) describe('no user associated with account', () => { it('returns status text', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { sendPasswordResetLink( input: { @@ -334,8 +326,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -355,7 +347,7 @@ describe('user send password reset email', () => { sendPasswordResetEmail: mockNotify, }, }, - ) + }) const expectedResult = { data: { diff --git a/api-js/src/user/mutations/__tests__/set-phone-number.test.js b/api/src/user/mutations/__tests__/set-phone-number.test.js similarity index 86% rename from api-js/src/user/mutations/__tests__/set-phone-number.test.js rename to api/src/user/mutations/__tests__/set-phone-number.test.js index dc1e1ee2e8..2c4e154b9e 100644 --- a/api-js/src/user/mutations/__tests__/set-phone-number.test.js +++ b/api/src/user/mutations/__tests__/set-phone-number.test.js @@ -1,16 +1,18 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import bcrypt from 'bcryptjs' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput, decryptPhoneNumber } from '../../../validators' import { tokenize, userRequired } from '../../../auth' import { loadUserByUserName, loadUserByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env const mockNotify = jest.fn() @@ -42,11 +44,15 @@ describe('user sets a new phone number', () => { let user beforeAll(async () => { ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) afterEach(async () => { @@ -75,16 +81,15 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) }) it('returns status text and updated user', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -102,13 +107,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -126,10 +131,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -153,12 +158,9 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) }) @@ -168,7 +170,6 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneDetails: {}, phoneValidated: true, tfaValidated: false, @@ -178,9 +179,9 @@ describe('user sets a new phone number', () => { }) it('returns status text and updated user', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -198,13 +199,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -222,10 +223,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -249,21 +250,16 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) - expect(decryptPhoneNumber(user.phoneDetails)).toEqual( - newPhoneNumber, - ) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) + expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) it('tfaSendMethod stays as none', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -281,13 +277,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -305,10 +301,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -318,9 +314,9 @@ describe('user sets a new phone number', () => { }) it('sets phoneValidated to false', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -338,13 +334,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -362,10 +358,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -379,7 +375,6 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneDetails: {}, phoneValidated: true, tfaValidated: false, @@ -389,9 +384,9 @@ describe('user sets a new phone number', () => { }) it('returns status text and update user', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -409,13 +404,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -433,10 +428,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -460,21 +455,16 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) - expect(decryptPhoneNumber(user.phoneDetails)).toEqual( - newPhoneNumber, - ) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) + expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) it('tfaSendMethod stays as email', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -492,13 +482,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -516,10 +506,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -529,9 +519,9 @@ describe('user sets a new phone number', () => { }) it('sets phoneValidated to false', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -549,13 +539,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -573,10 +563,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -590,7 +580,6 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneDetails: {}, phoneValidated: true, tfaValidated: false, @@ -600,9 +589,9 @@ describe('user sets a new phone number', () => { }) it('returns status text and updated user', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -620,13 +609,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -644,10 +633,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -671,21 +660,16 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) - expect(decryptPhoneNumber(user.phoneDetails)).toEqual( - newPhoneNumber, - ) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) + expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) it('sets tfaSendMethod to email', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -703,13 +687,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -727,10 +711,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -740,9 +724,9 @@ describe('user sets a new phone number', () => { }) it('sets phoneValidated to false', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -760,13 +744,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -784,10 +768,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -818,16 +802,15 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) }) it('returns status text and updated user', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -845,13 +828,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -869,10 +852,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -896,12 +879,9 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) }) @@ -911,7 +891,6 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneDetails: {}, phoneValidated: true, tfaValidated: false, @@ -921,9 +900,9 @@ describe('user sets a new phone number', () => { }) it('returns status text and updated user', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -941,13 +920,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -965,10 +944,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -992,21 +971,16 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) - expect(decryptPhoneNumber(user.phoneDetails)).toEqual( - newPhoneNumber, - ) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) + expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) it('tfaSendMethod stays as none', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1024,13 +998,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1048,10 +1022,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1061,9 +1035,9 @@ describe('user sets a new phone number', () => { }) it('sets phoneValidated to false', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1081,13 +1055,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1105,10 +1079,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1122,7 +1096,6 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneDetails: {}, phoneValidated: true, tfaValidated: false, @@ -1132,9 +1105,9 @@ describe('user sets a new phone number', () => { }) it('returns status text', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1152,13 +1125,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1176,10 +1149,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -1203,21 +1176,16 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) - expect(decryptPhoneNumber(user.phoneDetails)).toEqual( - newPhoneNumber, - ) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) + expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) it('tfaSendMethod stays as email', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1235,13 +1203,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1259,10 +1227,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1272,9 +1240,9 @@ describe('user sets a new phone number', () => { }) it('sets phoneValidated to false', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1292,13 +1260,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1316,10 +1284,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1333,7 +1301,6 @@ describe('user sets a new phone number', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneDetails: {}, phoneValidated: true, tfaValidated: false, @@ -1343,9 +1310,9 @@ describe('user sets a new phone number', () => { }) it('returns status text', async () => { const newPhoneNumber = '+12345678901' - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1363,13 +1330,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1387,10 +1354,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -1414,21 +1381,16 @@ describe('user sets a new phone number', () => { expect(response).toEqual(expectedResult) expect(mockNotify).toHaveBeenCalledWith({ - phoneNumber: newPhoneNumber, user, }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully set phone number.`, - ]) - expect(decryptPhoneNumber(user.phoneDetails)).toEqual( - newPhoneNumber, - ) + expect(consoleOutput).toEqual([`User: ${user._key} successfully set phone number.`]) + expect(decryptPhoneNumber(user.phoneDetails)).toEqual(newPhoneNumber) }) it('sets tfaSendMethod to email', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1446,13 +1408,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1470,10 +1432,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1483,9 +1445,9 @@ describe('user sets a new phone number', () => { }) it('sets phoneValidated to false', async () => { const newPhoneNumber = '+12345678901' - await graphql( + await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1503,13 +1465,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1527,10 +1489,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1564,14 +1526,13 @@ describe('user sets a new phone number', () => { const newPhoneNumber = '+12345678901' const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1589,13 +1550,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, query, - collections, + collections: collectionNames, transaction: mockedTransaction, auth: { bcrypt, @@ -1614,14 +1575,12 @@ describe('user sets a new phone number', () => { }, }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to set phone number, please try again.'), - ] + const error = [new GraphQLError('Unable to set phone number, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1637,14 +1596,13 @@ describe('user sets a new phone number', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1662,13 +1620,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, query, - collections, + collections: collectionNames, transaction: mockedTransaction, auth: { bcrypt, @@ -1685,14 +1643,12 @@ describe('user sets a new phone number', () => { }, }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to set phone number, please try again.'), - ] + const error = [new GraphQLError('Unable to set phone number, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1723,14 +1679,13 @@ describe('user sets a new phone number', () => { const newPhoneNumber = '+12345678901' const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1748,13 +1703,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, query, - collections, + collections: collectionNames, transaction: mockedTransaction, auth: { bcrypt, @@ -1773,16 +1728,12 @@ describe('user sets a new phone number', () => { }, }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de définir le numéro de téléphone, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de définir le numéro de téléphone, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1798,14 +1749,13 @@ describe('user sets a new phone number', () => { const mockedTransaction = jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { setPhoneNumber(input: { phoneNumber: "${newPhoneNumber}" }) { result { @@ -1823,13 +1773,13 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, query, - collections, + collections: collectionNames, transaction: mockedTransaction, auth: { bcrypt, @@ -1846,16 +1796,12 @@ describe('user sets a new phone number', () => { }, }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de définir le numéro de téléphone, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de définir le numéro de téléphone, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api-js/src/user/mutations/__tests__/sign-in.test.js b/api/src/user/mutations/__tests__/sign-in.test.js similarity index 77% rename from api-js/src/user/mutations/__tests__/sign-in.test.js rename to api/src/user/mutations/__tests__/sign-in.test.js index 667793de47..933c264e7c 100644 --- a/api-js/src/user/mutations/__tests__/sign-in.test.js +++ b/api/src/user/mutations/__tests__/sign-in.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import bcrypt from 'bcryptjs' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' @@ -7,18 +8,28 @@ import jwt from 'jsonwebtoken' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadUserByUserName } from '../../loaders' - -const { DB_PASS: rootPass, DB_URL: url, REFRESH_TOKEN_EXPIRY } = process.env +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' +import { tokenize } from '../../../auth' +import ms from 'ms' + +const { + DB_PASS: rootPass, + DB_URL: url, + REFRESH_TOKEN_EXPIRY, + SIGN_IN_KEY, + AUTH_TOKEN_EXPIRY, + REFRESH_KEY, +} = process.env const mockNotify = jest.fn() describe('authenticate user account', () => { - let query, drop, truncate, schema, i18n, tokenize, collections, transaction + let query, drop, truncate, schema, i18n, transaction const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) const mockedWarn = (output) => consoleOutput.push(output) @@ -33,7 +44,6 @@ describe('authenticate user account', () => { query: createQuerySchema(), mutation: createMutationSchema(), }) - tokenize = jest.fn().mockReturnValue('token') }) afterEach(() => { consoleOutput.length = 0 @@ -41,19 +51,22 @@ describe('authenticate user account', () => { describe('given a successful login', () => { beforeAll(async () => { // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + ;({ query, drop, truncate, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) - tokenize = jest.fn().mockReturnValue('token') }) beforeEach(async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -61,23 +74,21 @@ describe('authenticate user account', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - user { - id - } + ... on TFASignInResult { + authenticateToken + sendMethod } } } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, jwt, uuidv4, @@ -97,9 +108,10 @@ describe('authenticate user account', () => { request: { protocol: 'https', get: (text) => text, + ip: '127.0.0.1', }, }, - ) + }) }) afterEach(async () => { await truncate() @@ -122,6 +134,99 @@ describe('authenticate user account', () => { }, }) }) + describe('user has not logged in in the past 30 days', () => { + it('returns sendMethod message and authentication token', async () => { + let cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN MERGE({ id: user._key, _type: 'user' }, user) + ` + let user = await cursor.next() + + await query` + FOR user IN users + UPDATE ${user._key} WITH { tfaSendMethod: 'not_none', lastLogin: ${new Date( + new Date().setDate(new Date().getDate() - 31), + ).toISOString()} } IN users + ` + + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken) + + const response = await graphql({ + schema, + source: ` + mutation { + signIn( + input: { + userName: "test.account@istio.actually.exists" + password: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on AuthResult { + authToken + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockedTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const expectedResponse = { + data: { + signIn: { + result: { + sendMethod: 'email', + authenticateToken: authToken, + }, + }, + }, + } + + cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN MERGE({ id: user._key, _type: 'user' }, user) + ` + user = await cursor.next() + + expect(response).toEqual(expectedResponse) + expect(mockNotify).toHaveBeenCalledWith({ user }) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) + }) + }) describe('user has send method set to phone', () => { it('returns sendMethod message, authentication token and refresh token', async () => { let cursor = await query` @@ -136,9 +241,17 @@ describe('authenticate user account', () => { UPDATE ${user._key} WITH { tfaSendMethod: 'phone' } IN users ` - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -158,16 +271,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -179,14 +293,14 @@ describe('authenticate user account', () => { sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'text', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -203,9 +317,7 @@ describe('authenticate user account', () => { expect(mockNotify).toHaveBeenCalledWith({ user }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('user has send method set to email', () => { @@ -222,9 +334,17 @@ describe('authenticate user account', () => { UPDATE ${user._key} WITH { tfaSendMethod: 'email' } IN users ` - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -244,16 +364,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -265,14 +386,14 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'email', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -287,9 +408,7 @@ describe('authenticate user account', () => { expect(response).toEqual(expectedResponse) expect(mockNotify).toHaveBeenCalledWith({ user }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('user has send method set to none', () => { @@ -310,9 +429,22 @@ describe('authenticate user account', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: '456' }, + secret: String(REFRESH_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken).mockReturnValueOnce(refreshToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -332,17 +464,19 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, + jwt, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -354,13 +488,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -374,19 +508,13 @@ describe('authenticate user account', () => { user = await cursor.next() expect(response).toEqual(expectedResponse) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - expires: 0, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(mockedCookie).toHaveBeenCalledWith('refresh_token', refreshToken, { + httpOnly: true, + expires: 0, + sameSite: true, + secure: true, + }) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('user has rememberMe set to true', () => { @@ -406,9 +534,22 @@ describe('authenticate user account', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: '456' }, + secret: String(REFRESH_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken).mockReturnValueOnce(refreshToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -429,17 +570,19 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, + jwt, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -451,13 +594,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -471,19 +614,13 @@ describe('authenticate user account', () => { user = await cursor.next() expect(response).toEqual(expectedResponse) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(mockedCookie).toHaveBeenCalledWith('refresh_token', refreshToken, { + httpOnly: true, + maxAge: ms(REFRESH_TOKEN_EXPIRY), + sameSite: true, + secure: true, + }) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) }) @@ -501,9 +638,9 @@ describe('authenticate user account', () => { UPDATE ${user._key} WITH { phoneValidated: false, failedLoginAttempts: 5 } IN users ` - await graphql( + await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -523,13 +660,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -544,7 +682,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) cursor = await query` FOR user IN users @@ -586,9 +724,17 @@ describe('authenticate user account', () => { UPDATE ${user._key} WITH { tfaSendMethod: 'phone' } IN users ` - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -612,16 +758,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -633,14 +780,14 @@ describe('authenticate user account', () => { sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'text', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -655,9 +802,7 @@ describe('authenticate user account', () => { expect(response).toEqual(expectedResponse) expect(mockNotify).toHaveBeenCalledWith({ user }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('user has send method set to email', () => { @@ -674,9 +819,17 @@ describe('authenticate user account', () => { UPDATE ${user._key} WITH { tfaSendMethod: 'email' } IN users ` - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -700,16 +853,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -721,14 +875,14 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'email', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -743,9 +897,7 @@ describe('authenticate user account', () => { expect(response).toEqual(expectedResponse) expect(mockNotify).toHaveBeenCalledWith({ user }) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('user has send method set to none', () => { @@ -766,9 +918,22 @@ describe('authenticate user account', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: '456' }, + secret: String(REFRESH_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken).mockReturnValueOnce(refreshToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -788,17 +953,18 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -810,13 +976,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -830,19 +996,13 @@ describe('authenticate user account', () => { user = await cursor.next() expect(response).toEqual(expectedResponse) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - expires: 0, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(mockedCookie).toHaveBeenCalledWith('refresh_token', refreshToken, { + httpOnly: true, + expires: 0, + sameSite: true, + secure: true, + }) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('user has rememberMe set to true', () => { @@ -862,9 +1022,22 @@ describe('authenticate user account', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const authToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), + }) + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: '456' }, + secret: String(REFRESH_KEY), + }) + + const mockedTokenize = jest.fn().mockReturnValueOnce(authToken).mockReturnValueOnce(refreshToken) + + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -885,17 +1058,19 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, + jwt, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -907,13 +1082,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -927,19 +1102,13 @@ describe('authenticate user account', () => { user = await cursor.next() expect(response).toEqual(expectedResponse) - expect(mockedCookie).toHaveBeenCalledWith( - 'refresh_token', - 'token', - { - httpOnly: true, - maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - sameSite: true, - secure: true, - }, - ) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(mockedCookie).toHaveBeenCalledWith('refresh_token', refreshToken, { + httpOnly: true, + maxAge: ms(REFRESH_TOKEN_EXPIRY), + sameSite: true, + secure: true, + }) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) }) @@ -957,9 +1126,9 @@ describe('authenticate user account', () => { UPDATE ${user._key} WITH { phoneValidated: false, failedLoginAttempts: 5 } IN users ` - await graphql( + await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -983,11 +1152,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -1004,7 +1173,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) cursor = await query` FOR user IN users @@ -1036,9 +1205,9 @@ describe('authenticate user account', () => { }) describe('user cannot be found in database', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1062,11 +1231,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1084,15 +1253,14 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signIn: { result: { code: 400, - description: - 'Incorrect username or password. Please try again.', + description: 'Incorrect username or password. Please try again.', }, }, }, @@ -1106,9 +1274,9 @@ describe('authenticate user account', () => { }) describe('login credentials are invalid', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1132,14 +1300,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -1163,24 +1329,21 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signIn: { result: { code: 400, - description: - 'Incorrect username or password. Please try again.', + description: 'Incorrect username or password. Please try again.', }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User attempted to authenticate: 123 with invalid credentials.`, - ]) + expect(consoleOutput).toEqual([`User attempted to authenticate: 123 with invalid credentials.`]) }) it('increases the failed attempt counter', async () => { const user = { @@ -1190,9 +1353,9 @@ describe('authenticate user account', () => { failedLoginAttempts: 0, } - await graphql( + await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1216,14 +1379,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), - collections, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -1243,16 +1404,16 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) expect(user.failedLoginAttempts).toEqual(1) }) }) describe('user has reached maximum amount of login attempts', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1276,14 +1437,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -1308,32 +1467,29 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signIn: { result: { code: 401, - description: - 'Too many failed login attempts, please reset your password, and try again.', + description: 'Too many failed login attempts, please reset your password, and try again.', }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 tried to sign in, but has too many login attempts.`, - ]) + expect(consoleOutput).toEqual([`User: 123 tried to sign in, but has too many login attempts.`]) }) }) describe('transaction step error occurs', () => { describe('when resetting failed login attempts', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1357,16 +1513,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -1392,11 +1547,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1406,9 +1559,9 @@ describe('authenticate user account', () => { }) describe('when inserting tfa code', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1432,17 +1585,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -1468,11 +1619,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1482,9 +1631,9 @@ describe('authenticate user account', () => { }) describe('when setting refresh id', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1508,17 +1657,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -1545,11 +1692,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1559,9 +1704,9 @@ describe('authenticate user account', () => { }) describe('when incrementing failed login attempts', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1585,16 +1730,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -1621,10 +1765,8 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + }) + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1636,9 +1778,9 @@ describe('authenticate user account', () => { describe('transaction commit error occurs', () => { describe('during tfa sign in', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1662,16 +1804,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction Commit Error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction Commit Error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -1698,11 +1839,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1712,9 +1851,9 @@ describe('authenticate user account', () => { }) describe('during regular sign in', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1738,16 +1877,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction Commit Error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction Commit Error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -1774,11 +1912,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1788,9 +1924,9 @@ describe('authenticate user account', () => { }) describe('during failed login', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1814,16 +1950,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction Commit Error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction Commit Error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -1850,10 +1985,8 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) - const error = [ - new GraphQLError('Unable to sign in, please try again.'), - ] + }) + const error = [new GraphQLError('Unable to sign in, please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1880,9 +2013,9 @@ describe('authenticate user account', () => { }) describe('user cannot be found in database', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1906,11 +2039,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, auth: { bcrypt, @@ -1928,15 +2061,14 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signIn: { result: { code: 400, - description: - "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.", + description: "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.", }, }, }, @@ -1950,9 +2082,9 @@ describe('authenticate user account', () => { }) describe('login credentials are invalid', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1976,14 +2108,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -2007,24 +2137,21 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signIn: { result: { code: 400, - description: - "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.", + description: "Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.", }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User attempted to authenticate: 123 with invalid credentials.`, - ]) + expect(consoleOutput).toEqual([`User attempted to authenticate: 123 with invalid credentials.`]) }) it('increases the failed attempt counter', async () => { const user = { @@ -2034,9 +2161,9 @@ describe('authenticate user account', () => { failedLoginAttempts: 0, } - await graphql( + await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2060,14 +2187,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), - collections, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -2087,16 +2212,16 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) expect(user.failedLoginAttempts).toEqual(1) }) }) describe('user has reached maximum amount of login attempts', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2120,14 +2245,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -2152,7 +2275,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -2167,17 +2290,15 @@ describe('authenticate user account', () => { } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 tried to sign in, but has too many login attempts.`, - ]) + expect(consoleOutput).toEqual([`User: 123 tried to sign in, but has too many login attempts.`]) }) }) describe('transaction step error occurs', () => { describe('when resetting failed login attempts', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2201,16 +2322,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -2236,13 +2356,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2252,9 +2368,9 @@ describe('authenticate user account', () => { }) describe('when inserting tfa code', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2278,17 +2394,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -2314,13 +2428,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2330,9 +2440,9 @@ describe('authenticate user account', () => { }) describe('when setting refresh id', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2356,17 +2466,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -2393,13 +2501,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2409,9 +2513,9 @@ describe('authenticate user account', () => { }) describe('when incrementing failed login attempts', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2435,16 +2539,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction Step Error')), + step: jest.fn().mockRejectedValue(new Error('Transaction Step Error')), commit: jest.fn(), + abort: jest.fn(), }), uuidv4, auth: { @@ -2471,12 +2574,8 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + }) + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2488,9 +2587,9 @@ describe('authenticate user account', () => { describe('transaction commit error occurs', () => { describe('during tfa sign in', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2514,16 +2613,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction Commit Error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction Commit Error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -2550,13 +2648,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2566,9 +2660,9 @@ describe('authenticate user account', () => { }) describe('during regular sign in', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2592,16 +2686,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction Commit Error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction Commit Error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -2628,13 +2721,9 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2644,9 +2733,9 @@ describe('authenticate user account', () => { }) describe('during failed login', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2670,16 +2759,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction Commit Error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction Commit Error')), + abort: jest.fn(), }), uuidv4, auth: { @@ -2706,12 +2794,8 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) - const error = [ - new GraphQLError( - 'Impossible de se connecter, veuillez réessayer.', - ), - ] + }) + const error = [new GraphQLError('Impossible de se connecter, veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api-js/src/user/mutations/__tests__/sign-out.test.js b/api/src/user/mutations/__tests__/sign-out.test.js similarity index 92% rename from api-js/src/user/mutations/__tests__/sign-out.test.js rename to api/src/user/mutations/__tests__/sign-out.test.js index e099130a3e..d2344384e5 100644 --- a/api-js/src/user/mutations/__tests__/sign-out.test.js +++ b/api/src/user/mutations/__tests__/sign-out.test.js @@ -33,21 +33,21 @@ describe('signing the user out', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signOut(input: {}) { status } } `, - null, - { + rootValue: null, + contextValue: { i18n, response: mockedResponse, }, - ) + }) const expectedResult = { data: { @@ -85,21 +85,21 @@ describe('signing the user out', () => { const mockedCookie = jest.fn() const mockedResponse = { cookie: mockedCookie } - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signOut(input: {}) { status } } `, - null, - { + rootValue: null, + contextValue: { i18n, response: mockedResponse, }, - ) + }) const expectedResult = { data: { diff --git a/api/src/user/mutations/__tests__/sign-up.test.js b/api/src/user/mutations/__tests__/sign-up.test.js new file mode 100644 index 0000000000..7dd4b0c961 --- /dev/null +++ b/api/src/user/mutations/__tests__/sign-up.test.js @@ -0,0 +1,3309 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import bcrypt from 'bcryptjs' +import { graphql, GraphQLError, GraphQLSchema } from 'graphql' +import { setupI18n } from '@lingui/core' +import { v4 as uuidv4 } from 'uuid' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { tokenize, verifyToken } from '../../../auth' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { loadUserByUserName, loadUserByKey } from '../../loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, REFRESH_TOKEN_EXPIRY, TRACKER_PRODUCTION } = process.env + +describe('testing user sign up', () => { + let query, drop, truncate, collections, transaction, schema, i18n, mockTokenize, mockNotify + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + mockTokenize = jest.fn().mockReturnValue('token') + }) + beforeEach(() => { + mockNotify = jest.fn() + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful sign up', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given a successful sign up', () => { + describe('when user is not signing up without an invite token', () => { + describe('user has rememberMe disabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + describe('user has rememberMe enabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + rememberMe: true + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + request: { ip: '127.0.0.1' }, + uuidv4, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + }) + describe('when the user is signing up with an invite token', () => { + let org, token + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: org._key, + requestedRole: 'admin', + }, + }) + }) + describe('user has rememberMe disabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + request: { ip: '127.0.0.1' }, + uuidv4, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('creates affiliation', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const affiliationCursor = await query` + FOR affiliation IN affiliations + FILTER affiliation._to == ${user._id} + RETURN affiliation + ` + const checkAffiliation = await affiliationCursor.next() + + const expectedAffiliation = { + _from: org._id, + _to: user._id, + permission: 'admin', + } + + expect(checkAffiliation).toMatchObject(expectedAffiliation) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + describe('user has rememberMe enabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + rememberMe: true + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + request: { ip: '127.0.0.1' }, + uuidv4, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('creates affiliation', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const affiliationCursor = await query` + FOR affiliation IN affiliations + FILTER affiliation._to == ${user._id} + RETURN affiliation + ` + const checkAffiliation = await affiliationCursor.next() + + const expectedAffiliation = { + _from: org._id, + _to: user._id, + permission: 'admin', + } + + expect(checkAffiliation).toMatchObject(expectedAffiliation) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given successful sign up', () => { + describe('when user is not signing up without an invite token', () => { + describe('user has rememberMe disabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + request: { ip: '127.0.0.1' }, + uuidv4, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + describe('user has rememberMe enabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + rememberMe: true + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + request: { ip: '127.0.0.1' }, + uuidv4, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + }) + describe('when the user is signing up with an invite token', () => { + let org, token + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: org._key, + requestedRole: 'admin', + }, + }) + }) + describe('user has rememberMe disabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('creates affiliation', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const affiliationCursor = await query` + FOR affiliation IN affiliations + FILTER affiliation._to == ${user._id} + RETURN affiliation + ` + const checkAffiliation = await affiliationCursor.next() + + const expectedAffiliation = { + _from: org._id, + _to: user._id, + permission: 'admin', + } + + expect(checkAffiliation).toMatchObject(expectedAffiliation) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + describe('user has rememberMe enabled', () => { + it('returns auth result with user info', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + rememberMe: true + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const users = await cursor.all() + expect(users).toHaveLength(1) + + const expectedResult = { + data: { + signUp: { + result: { + authenticateToken: 'token', + sendMethod: 'email', + }, + }, + }, + } + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', + ]) + }) + it('creates affiliation', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const affiliationCursor = await query` + FOR affiliation IN affiliations + FILTER affiliation._to == ${user._id} + RETURN affiliation + ` + const checkAffiliation = await affiliationCursor.next() + + const expectedAffiliation = { + _from: org._id, + _to: user._id, + permission: 'admin', + } + + expect(checkAffiliation).toMatchObject(expectedAffiliation) + }) + it('sends verification email', async () => { + await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n: {} }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const user = await loadUserByUserName({ + query, + userKey: '1', + i18n: {}, + }).load('test.account@istio.actually.exists') + + expect(mockNotify).toHaveBeenCalledWith({ + user: user, + }) + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful sign up', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('when the password is not strong enough', () => { + it('returns a password too short error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "123" + confirmPassword: "123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Password does not meet requirements.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up but did not meet requirements.', + ]) + }) + }) + describe('when the passwords do not match', () => { + it('returns a password not matching error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "321drowssaptset" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Passwords do not match.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up but passwords do not match.', + ]) + }) + }) + describe('when the user name (email) already in use', () => { + it('returns an email already in use error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + phoneValidated: false, + emailValidated: false, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Email already in use.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up, however there is already an account in use with that email.', + ]) + }) + }) + describe('user is signing up with invite token', () => { + describe('when the invite token user name and submitted user name do not match', () => { + let token + beforeEach(() => { + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: '123', + requestedRole: 'admin', + }, + }) + }) + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test@email.ca" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ + next: jest.fn(), + }), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Unable to sign up, please contact org admin for a new invite.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test@email.ca attempted to sign up with an invite token, however emails do not match.', + ]) + }) + }) + describe('when the invite token org key is not a defined org', () => { + let token + beforeEach(() => { + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: '456', + requestedRole: 'admin', + }, + }) + }) + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ + next: jest.fn(), + }), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Unable to sign up, please contact org admin for a new invite.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists attempted to sign up with an invite token, however the org could not be found.', + ]) + }) + }) + }) + describe('given a cursor error', () => { + describe('when gathering inserted user', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ + next: jest.fn().mockRejectedValue('Cursor Error'), + }), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError('Unable to sign up. Please try again.')] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Cursor error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Cursor Error', + ]) + }) + }) + }) + describe('given a transaction error', () => { + describe('when inserting user', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('Transaction Step Error'), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError('Unable to sign up. Please try again.')] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Transaction Step Error', + ]) + }) + }) + describe('when inserting affiliation', () => { + let token + beforeEach(() => { + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: '123', + requestedRole: 'admin', + }, + }) + }) + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValueOnce({ next: jest.fn() }).mockRejectedValue('Transaction Step Error'), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError('Unable to sign up. Please try again.')] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, assigning affiliation: Transaction Step Error', + ]) + }) + }) + describe('when committing transaction', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ next: jest.fn() }), + commit: jest.fn().mockRejectedValue('Transaction Commit Error'), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError('Unable to sign up. Please try again.')] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Transaction commit error occurred while user: test.account@istio.actually.exists attempted to sign up: Transaction Commit Error', + ]) + }) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('when the password is not strong enough', () => { + it('returns a password too short error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "123" + confirmPassword: "123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Le mot de passe ne répond pas aux exigences.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up but did not meet requirements.', + ]) + }) + }) + describe('when the passwords do not match', () => { + it('returns a password not matching error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "321drowssaptset" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Les mots de passe ne correspondent pas.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up but passwords do not match.', + ]) + }) + }) + describe('when the user name (email) already in use', () => { + it('returns an email already in use error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + phoneValidated: false, + emailValidated: false, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: 'Courriel déjà utilisé.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists tried to sign up, however there is already an account in use with that email.', + ]) + }) + }) + describe('user is signing up with invite token', () => { + describe('when the invite token user name and submitted user name do not match', () => { + let token + beforeEach(() => { + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: '123', + requestedRole: 'admin', + }, + }) + }) + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test@email.ca" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ + next: jest.fn(), + }), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: + "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test@email.ca attempted to sign up with an invite token, however emails do not match.', + ]) + }) + }) + describe('when the invite token org key is not a defined org', () => { + let token + beforeEach(() => { + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: '456', + requestedRole: 'admin', + }, + }) + }) + it('returns an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ + next: jest.fn(), + }), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = { + data: { + signUp: { + result: { + code: 400, + description: + "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + 'User: test.account@istio.actually.exists attempted to sign up with an invite token, however the org could not be found.', + ]) + }) + }) + }) + describe('given a cursor error', () => { + describe('when gathering inserted user', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ + next: jest.fn().mockRejectedValue('Cursor Error'), + }), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Cursor error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Cursor Error', + ]) + }) + }) + }) + describe('given a transaction error', () => { + describe('when inserting user', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('Transaction Step Error'), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, creating user: Transaction Step Error', + ]) + }) + }) + describe('when inserting affiliation', () => { + let token + beforeEach(() => { + token = tokenize({ + parameters: { + userName: 'test.account@istio.actually.exists', + orgKey: '123', + requestedRole: 'admin', + }, + }) + }) + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + signUpToken: "${token}" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValueOnce({ next: jest.fn() }).mockRejectedValue('Transaction Step Error'), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Transaction step error occurred while user: test.account@istio.actually.exists attempted to sign up, assigning affiliation: Transaction Step Error', + ]) + }) + }) + describe('when committing transaction', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + signUp( + input: { + displayName: "Test Account" + userName: "test.account@istio.actually.exists" + password: "testpassword123" + confirmPassword: "testpassword123" + } + ) { + result { + ... on TFASignInResult { + authenticateToken + sendMethod + } + ... on SignUpError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({ next: jest.fn() }), + commit: jest.fn().mockRejectedValue('Transaction Commit Error'), + abort: jest.fn(), + }), + uuidv4, + request: { ip: '127.0.0.1' }, + auth: { + bcrypt, + tokenize: mockTokenize, + verifyToken: verifyToken({ i18n }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn(), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { + sendAuthEmail: mockNotify, + }, + }, + }) + + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] + + expect(response.errors).toEqual(error) + + expect(consoleOutput).toEqual([ + 'Transaction commit error occurred while user: test.account@istio.actually.exists attempted to sign up: Transaction Commit Error', + ]) + }) + }) + }) + }) + }) +}) diff --git a/api-js/src/user/mutations/__tests__/update-user-password.test.js b/api/src/user/mutations/__tests__/update-user-password.test.js similarity index 84% rename from api-js/src/user/mutations/__tests__/update-user-password.test.js rename to api/src/user/mutations/__tests__/update-user-password.test.js index 4b75507aff..b3910e594f 100644 --- a/api-js/src/user/mutations/__tests__/update-user-password.test.js +++ b/api/src/user/mutations/__tests__/update-user-password.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import bcrypt from 'bcryptjs' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' @@ -7,19 +8,20 @@ import jwt from 'jsonwebtoken' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { tokenize, userRequired } from '../../../auth' import { loadUserByUserName, loadUserByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env const mockNotfiy = jest.fn() describe('authenticate user account', () => { - let query, drop, truncate, schema, i18n, user, collections, transaction + let query, drop, truncate, schema, i18n, user, transaction const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) const mockedWarn = (output) => consoleOutput.push(output) @@ -42,18 +44,22 @@ describe('authenticate user account', () => { describe('given a successful update', () => { beforeAll(async () => { // Generate DB Items - ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + ;({ query, drop, truncate, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -61,23 +67,21 @@ describe('authenticate user account', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - user { - id - } + ... on TFASignInResult { + authenticateToken + sendMethod } } } } `, - null, - { + rootValue: null, + contextValue: { query, - collections, + collections: collectionNames, transaction, jwt, uuidv4, @@ -99,7 +103,7 @@ describe('authenticate user account', () => { get: (text) => text, }, }, - ) + }) const userCursor = await query` FOR user IN users FILTER user.userName == "test.account@istio.actually.exists" @@ -130,9 +134,9 @@ describe('authenticate user account', () => { }) }) it('returns a successful status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -153,13 +157,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -176,7 +181,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -189,15 +194,13 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their password.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their password.`]) consoleOutput.length = 0 - const authenticateResponse = await graphql( + const authenticateResponse = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -213,11 +216,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -234,7 +237,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotfiy, }, }, - ) + }) const expectedAuthResponse = { data: { @@ -247,9 +250,7 @@ describe('authenticate user account', () => { } expect(authenticateResponse).toEqual(expectedAuthResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) describe('users language is set to french', () => { @@ -268,9 +269,9 @@ describe('authenticate user account', () => { }) }) it('returns a successful status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -291,13 +292,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -314,7 +316,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -327,15 +329,13 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their password.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their password.`]) consoleOutput.length = 0 - const authenticateResponse = await graphql( + const authenticateResponse = await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -351,11 +351,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, uuidv4, auth: { @@ -372,7 +372,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotfiy, }, }, - ) + }) const expectedAuthResponse = { data: { @@ -385,9 +385,7 @@ describe('authenticate user account', () => { } expect(authenticateResponse).toEqual(expectedAuthResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully signed in, and sent auth msg.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully signed in, and sent auth msg.`]) }) }) }) @@ -409,9 +407,9 @@ describe('authenticate user account', () => { }) describe('the current password does not match the password in the database', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -432,11 +430,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, auth: { @@ -452,15 +450,14 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { updateUserPassword: { result: { code: 400, - description: - 'Unable to update password, current password does not match. Please try again.', + description: 'Unable to update password, current password does not match. Please try again.', }, }, }, @@ -474,9 +471,9 @@ describe('authenticate user account', () => { }) describe('the new password does not match the new password confirmation', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -497,11 +494,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, auth: { @@ -517,15 +514,14 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { updateUserPassword: { result: { code: 400, - description: - 'Unable to update password, new passwords do not match. Please try again.', + description: 'Unable to update password, new passwords do not match. Please try again.', }, }, }, @@ -539,9 +535,9 @@ describe('authenticate user account', () => { }) describe('the new password does not meet GoC requirements', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -562,11 +558,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, auth: { @@ -582,15 +578,14 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { updateUserPassword: { result: { code: 400, - description: - 'Unable to update password, passwords do not match requirements. Please try again.', + description: 'Unable to update password, passwords do not match requirements. Please try again.', }, }, }, @@ -605,9 +600,9 @@ describe('authenticate user account', () => { describe('transaction step error occurs', () => { describe('when updating password', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -628,15 +623,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), userKey: 123, auth: { @@ -653,11 +647,9 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update password. Please try again.'), - ] + const error = [new GraphQLError('Unable to update password. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -669,9 +661,9 @@ describe('authenticate user account', () => { describe('transaction commit error occurs', () => { describe('when updating password', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -692,16 +684,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), userKey: 123, auth: { @@ -718,11 +709,9 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update password. Please try again.'), - ] + const error = [new GraphQLError('Unable to update password. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -749,9 +738,9 @@ describe('authenticate user account', () => { }) describe('the current password does not match the password in the database', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -772,11 +761,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, auth: { @@ -792,7 +781,7 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { @@ -814,9 +803,9 @@ describe('authenticate user account', () => { }) describe('the new password does not match the new password confirmation', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -837,11 +826,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, auth: { @@ -857,7 +846,7 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { @@ -879,9 +868,9 @@ describe('authenticate user account', () => { }) describe('the new password does not meet GoC requirements', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -902,11 +891,11 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction, userKey: 123, auth: { @@ -922,7 +911,7 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { @@ -945,9 +934,9 @@ describe('authenticate user account', () => { describe('transaction step error occurs', () => { describe('when updating password', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -968,15 +957,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), userKey: 123, auth: { @@ -993,13 +981,9 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le mot de passe. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le mot de passe. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1011,9 +995,9 @@ describe('authenticate user account', () => { describe('transaction commit error occurs', () => { describe('when updating password', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserPassword( input: { @@ -1034,16 +1018,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), userKey: 123, auth: { @@ -1060,13 +1043,9 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le mot de passe. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le mot de passe. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/user/mutations/__tests__/update-user-profile.test.js b/api/src/user/mutations/__tests__/update-user-profile.test.js new file mode 100644 index 0000000000..ad8bfb23bf --- /dev/null +++ b/api/src/user/mutations/__tests__/update-user-profile.test.js @@ -0,0 +1,1925 @@ +import bcrypt from 'bcryptjs' +import crypto from 'crypto' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { tokenize, userRequired } from '../../../auth' +import { loadUserByUserName, loadUserByKey } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, CIPHER_KEY, AUTHENTICATED_KEY, AUTH_TOKEN_EXPIRY } = process.env + +describe('authenticate user account', () => { + let query, drop, truncate, collections, transaction, schema, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given a successful update', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + tfaSendMethod: 'none', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user updates their display name', () => { + it('returns a successful status message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { displayName: "John Doe" }) { + result { + ... on UpdateUserProfileResult { + status + user { + displayName + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + displayName: 'John Doe', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + describe('user updates their user name', () => { + it('returns a successful status message and sends verify email link', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const sendVerificationEmail = jest.fn() + const newUsername = 'john.doe@istio.actually.works' + + const verifyUrl = `https://domain.ca/validate/${tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key, userName: newUsername }, + secret: String(AUTHENTICATED_KEY), + })}` + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { userName: "${newUsername}" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + userName + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { + get: jest.fn().mockReturnValue('domain.ca'), + ip: '127.0.0.1', + }, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail }, + }, + }) + + // Does not change userName + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + userName: 'test.account@istio.actually.exists', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(sendVerificationEmail).toHaveBeenCalledWith({ + verifyUrl: verifyUrl, + userKey: user._key, + displayName: user.displayName, + userName: newUsername, + }) + + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + describe('user is not email validated', () => { + it('does not change emailValidated value', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + UPDATE user._key WITH { + emailValidated: false, + } IN users + RETURN NEW + ` + const user = await cursor.next() + + await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { userName: "john.doe@istio.actually.works" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + userName + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { + get: jest.fn().mockReturnValue('domain.ca'), + }, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const checkCursor = await query` + FOR user IN users + RETURN user + ` + const checkUser = await checkCursor.next() + + expect(checkUser.emailValidated).toBeFalsy() + }) + }) + }) + describe('user attempts to update their tfa send method', () => { + describe('user attempts to set to phone', () => { + describe('user is phone validated', () => { + beforeEach(async () => { + await truncate() + + const updatedPhoneDetails = { + iv: crypto.randomBytes(12).toString('hex'), + } + const cipher = crypto.createCipheriv( + 'aes-256-ccm', + String(CIPHER_KEY), + Buffer.from(updatedPhoneDetails.iv, 'hex'), + { authTagLength: 16 }, + ) + let encrypted = cipher.update('+12345678998', 'utf8', 'hex') + encrypted += cipher.final('hex') + + updatedPhoneDetails.phoneNumber = encrypted + updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') + + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + phoneValidated: true, + tfaSendMethod: 'none', + phoneDetails: updatedPhoneDetails, + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: PHONE }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'PHONE', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + describe('user is not phone validated', () => { + beforeEach(async () => { + await truncate() + + const updatedPhoneDetails = { + iv: crypto.randomBytes(12).toString('hex'), + } + const cipher = crypto.createCipheriv( + 'aes-256-ccm', + String(CIPHER_KEY), + Buffer.from(updatedPhoneDetails.iv, 'hex'), + { authTagLength: 16 }, + ) + let encrypted = cipher.update('+12345678998', 'utf8', 'hex') + encrypted += cipher.final('hex') + + updatedPhoneDetails.phoneNumber = encrypted + updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') + + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + phoneValidated: false, + tfaSendMethod: 'none', + phoneDetails: updatedPhoneDetails, + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: PHONE }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'NONE', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + }) + describe('user attempts to set to email', () => { + describe('user is email validated', () => { + beforeEach(async () => { + await truncate() + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'none', + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: EMAIL }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'EMAIL', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + describe('user is not email validated', () => { + beforeEach(async () => { + await truncate() + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: false, + tfaSendMethod: 'none', + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: EMAIL }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'NONE', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + }) + describe('user attempts to set to none', () => { + beforeEach(async () => { + await truncate() + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: NONE }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'NONE', + }, + status: 'Profile successfully updated.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user updates their display name', () => { + it('returns a successful status message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { displayName: "John Doe" }) { + result { + ... on UpdateUserProfileResult { + status + user { + displayName + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + displayName: 'John Doe', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + describe('user updates their user name', () => { + describe('user updates their user name', () => { + it('returns a successful status message and sends verify email link', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const sendVerificationEmail = jest.fn() + const newUsername = 'john.doe@istio.actually.works' + + const verifyUrl = `https://domain.ca/validate/${tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key, userName: newUsername }, + secret: String(AUTHENTICATED_KEY), + })}` + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { userName: "${newUsername}" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + userName + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { + get: jest.fn().mockReturnValue('domain.ca'), + ip: '127.0.0.1', + }, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + userName: 'test.account@istio.actually.exists', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + + expect(sendVerificationEmail).toHaveBeenCalledWith({ + verifyUrl: verifyUrl, + userKey: user._key, + displayName: user.displayName, + userName: newUsername, + }) + + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + + describe('user is not email validated', () => { + it('does not change emailValidated value', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + UPDATE user._key WITH { + emailValidated: false, + } IN users + RETURN NEW + ` + const user = await cursor.next() + + await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { userName: "john.doe@istio.actually.works" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + userName + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + request: { + get: jest.fn().mockReturnValue('domain.ca'), + }, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const checkCursor = await query` + FOR user IN users + RETURN user + ` + const checkUser = await checkCursor.next() + + expect(checkUser.emailValidated).toBeFalsy() + }) + }) + }) + }) + describe('user attempts to update their tfa send method', () => { + describe('user attempts to set to phone', () => { + describe('user is phone validated', () => { + beforeEach(async () => { + await truncate() + + const updatedPhoneDetails = { + iv: crypto.randomBytes(12).toString('hex'), + } + const cipher = crypto.createCipheriv( + 'aes-256-ccm', + String(CIPHER_KEY), + Buffer.from(updatedPhoneDetails.iv, 'hex'), + { authTagLength: 16 }, + ) + let encrypted = cipher.update('+12345678998', 'utf8', 'hex') + encrypted += cipher.final('hex') + + updatedPhoneDetails.phoneNumber = encrypted + updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') + + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + phoneValidated: true, + tfaSendMethod: 'none', + phoneDetails: updatedPhoneDetails, + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: PHONE }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'PHONE', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + describe('user is not phone validated', () => { + beforeEach(async () => { + await truncate() + + const updatedPhoneDetails = { + iv: crypto.randomBytes(12).toString('hex'), + } + const cipher = crypto.createCipheriv( + 'aes-256-ccm', + String(CIPHER_KEY), + Buffer.from(updatedPhoneDetails.iv, 'hex'), + { authTagLength: 16 }, + ) + let encrypted = cipher.update('+12345678998', 'utf8', 'hex') + encrypted += cipher.final('hex') + + updatedPhoneDetails.phoneNumber = encrypted + updatedPhoneDetails.tag = cipher.getAuthTag().toString('hex') + + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + phoneValidated: false, + tfaSendMethod: 'none', + phoneDetails: updatedPhoneDetails, + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: PHONE }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'NONE', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + }) + describe('user attempts to set to email', () => { + describe('user is email validated', () => { + beforeEach(async () => { + await truncate() + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'none', + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: EMAIL }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'EMAIL', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + describe('user is not email validated', () => { + beforeEach(async () => { + await truncate() + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: false, + tfaSendMethod: 'none', + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: EMAIL }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'NONE', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + }) + describe('user attempts to set to none', () => { + beforeEach(async () => { + await truncate() + await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + it('returns message, and the updated user info', async () => { + const cursor = await query` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` + const user = await cursor.next() + + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile(input: { tfaSendMethod: NONE }) { + result { + ... on UpdateUserProfileResult { + status + user { + tfaSendMethod + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + bcrypt, + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: loadUserByUserName({ query }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const expectedResponse = { + data: { + updateUserProfile: { + result: { + user: { + tfaSendMethod: 'NONE', + }, + status: 'Le profil a été mis à jour avec succès.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) + }) + }) + }) + }) + }) + describe('given an unsuccessful update', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user attempts to set email to one that is already in use', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { userName: "john.doe@istio.actually.works" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + id + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn(), + userKey: 123, + auth: { + bcrypt, + tokenize, + userRequired: jest.fn().mockReturnValue({ + tfaSendMethod: 'none', + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const error = { + data: { + updateUserProfile: { + result: { + code: 400, + description: 'Username not available, please try another.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update their username, but the username is already in use.`, + ]) + }) + }) + describe('given a transaction step error', () => { + describe('when updating profile', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { + displayName: "John Smith" + userName: "john.smith@istio.actually.works" + } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + id + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), + }), + userKey: 123, + auth: { + bcrypt, + tokenize, + userRequired: jest.fn().mockReturnValue({ + tfaSendMethod: 'none', + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const error = [new GraphQLError('Unable to update profile. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when user: 123 attempted to update their profile: Error: Transaction step error`, + ]) + }) + }) + }) + describe('given a transaction step error', () => { + describe('when updating profile', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { + displayName: "John Smith" + userName: "john.smith@istio.actually.works" + } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + id + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({}), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), + }), + userKey: 123, + auth: { + bcrypt, + tokenize, + userRequired: jest.fn().mockReturnValue({ + tfaSendMethod: 'none', + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const error = [new GraphQLError('Unable to update profile. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to update their profile: Error: Transaction commit error`, + ]) + }) + }) + }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user attempts to set email to one that is already in use', () => { + it('returns an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { userName: "john.doe@istio.actually.works" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + id + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn(), + userKey: 123, + auth: { + bcrypt, + tokenize, + userRequired: jest.fn().mockReturnValue({ + tfaSendMethod: 'none', + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue({}), + }, + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const error = { + data: { + updateUserProfile: { + result: { + code: 400, + description: "Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.", + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to update their username, but the username is already in use.`, + ]) + }) + }) + describe('given a transaction step error', () => { + describe('when updating profile', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { + displayName: "John Smith" + userName: "john.smith@istio.actually.works" + } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + id + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), + }), + userKey: 123, + auth: { + bcrypt, + tokenize, + userRequired: jest.fn().mockReturnValue({ + tfaSendMethod: 'none', + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const error = [new GraphQLError('Impossible de mettre à jour le profil. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when user: 123 attempted to update their profile: Error: Transaction step error`, + ]) + }) + }) + }) + describe('given a transaction step error', () => { + describe('when updating profile', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + updateUserProfile( + input: { + displayName: "John Smith" + userName: "john.smith@istio.actually.works" + } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + id + } + } + ... on UpdateUserProfileError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({}), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), + }), + userKey: 123, + auth: { + bcrypt, + tokenize, + userRequired: jest.fn().mockReturnValue({ + tfaSendMethod: 'none', + }), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByUserName: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + notify: { sendVerificationEmail: jest.fn() }, + }, + }) + + const error = [new GraphQLError('Impossible de mettre à jour le profil. Veuillez réessayer.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to update their profile: Error: Transaction commit error`, + ]) + }) + }) + }) + }) + }) +}) diff --git a/api/src/user/mutations/__tests__/verify-account.test.js b/api/src/user/mutations/__tests__/verify-account.test.js new file mode 100644 index 0000000000..ec71acb6cd --- /dev/null +++ b/api/src/user/mutations/__tests__/verify-account.test.js @@ -0,0 +1,518 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { tokenize, verifyToken } from '../../../auth' +import { loadUserByKey, loadUserByUserName } from '../../loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('user send password reset email', () => { + let query, drop, truncate, collections, transaction, schema, request, i18n + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + request = { + protocol: 'https', + get: (text) => text, + } + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + afterEach(async () => { + consoleOutput.length = 0 + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('given a successful validation', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }) + }) + it('returns a successful status message and update username', async () => { + const previousUserName = 'test.account@istio.actually.exists' + let cursor = await query` + FOR user IN users + FILTER user.userName == ${previousUserName} + RETURN user + ` + let user = await cursor.next() + + const newUserName = 'john.doe@istio.actually.works' + + const token = tokenize({ parameters: { userKey: user._key, userName: newUserName } }) + + const sendUpdatedUserNameEmail = jest.fn() + + const response = await graphql({ + schema, + source: ` + mutation { + verifyAccount(input: { verifyTokenString: "${token}" }) { + result { + ... on VerifyAccountResult { + status + } + ... on VerifyAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request, + userKey: user._key, + query, + collections: collectionNames, + transaction, + auth: { + verifyToken: verifyToken({}), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + }, + notify: { sendUpdatedUserNameEmail }, + }, + }) + + const expectedResult = { + data: { + verifyAccount: { + result: { + status: 'Successfully email verified account.', + }, + }, + }, + } + + expect(sendUpdatedUserNameEmail).toHaveBeenCalledWith({ + previousUserName: previousUserName, + newUserName: newUserName, + displayName: user.displayName, + userKey: user._key, + }) + + cursor = await query` + FOR user IN users + FILTER user.userName == ${newUserName} + RETURN user + ` + user = await cursor.next() + + expect(user.emailValidated).toEqual(true) + + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully email validated their account.`]) + }) + }) + }) + describe('given an unsuccessful validation', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('userKey cannot be found in token parameters', () => { + it('returns an error message', async () => { + const token = tokenize({ + parameters: {}, + }) + + const response = await graphql({ + schema, + source: ` + mutation { + verifyAccount(input: { verifyTokenString: "${token}" }) { + result { + ... on VerifyAccountResult { + status + } + ... on VerifyAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request, + userKey: 123, + query, + collections: collectionNames, + transaction, + auth: { + verifyToken: verifyToken({}), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByKey: { + load: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }), + }, + loadUserByUserName: loadUserByUserName({ query }), + }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, + }, + }) + + const error = { + data: { + verifyAccount: { + result: { + code: 400, + description: 'Unable to verify account. Please request a new email.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, + ]) + }) + }) + describe('userKey in token is undefined', () => { + it('returns an error message', async () => { + const token = tokenize({ + parameters: { userKey: undefined }, + }) + + const response = await graphql({ + schema, + source: ` + mutation { + verifyAccount(input: { verifyTokenString: "${token}" }) { + result { + ... on VerifyAccountResult { + status + } + ... on VerifyAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request, + userKey: 123, + query, + collections: collectionNames, + transaction, + auth: { + verifyToken: verifyToken({}), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByKey: { + load: jest.fn().mockReturnValue({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }), + }, + loadUserByUserName: loadUserByUserName({ query }), + }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, + }, + }) + + const error = { + data: { + verifyAccount: { + result: { + code: 400, + description: 'Unable to verify account. Please request a new email.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, + ]) + }) + }) + describe('user cannot be found in db', () => { + it('returns an error message', async () => { + const token = tokenize({ + parameters: { userKey: 1, userName: 'john.doe@istio.actually.exists' }, + }) + const response = await graphql({ + schema, + source: ` + mutation { + verifyAccount(input: { verifyTokenString: "${token}" }) { + result { + ... on VerifyAccountResult { + status + } + ... on VerifyAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request, + userKey: 1, + query, + collections: collectionNames, + transaction, + auth: { + verifyToken: verifyToken({}), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByUserName: loadUserByUserName({ query }), + }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, + }, + }) + + const error = { + data: { + verifyAccount: { + result: { + code: 400, + description: 'Unable to verify account. Please request a new email.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 1 attempted to verify account, however no account is associated with this id.`, + ]) + }) + }) + describe('transaction step error occurs', () => { + describe('when upserting validation', () => { + it('throws an error', async () => { + const token = tokenize({ + parameters: { userKey: 123, userName: 'john.doe@istio.actually.exists' }, + }) + + const response = await graphql({ + schema, + source: ` + mutation { + verifyAccount(input: { verifyTokenString: "${token}" }) { + result { + ... on VerifyAccountResult { + status + } + ... on VerifyAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request, + userKey: 123, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue(new Error('Transaction step error occurred.')), + abort: jest.fn(), + }), + auth: { + verifyToken: verifyToken({}), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }), + }, + loadUserByUserName: loadUserByUserName({ query, userKey: 123, i18n }), + }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, + }, + }) + + const error = [new GraphQLError('Unable to verify account. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx step error occurred when upserting email validation for user: 123: Error: Transaction step error occurred.`, + ]) + }) + }) + }) + describe('transaction commit error occurs', () => { + describe('when upserting validation', () => { + it('throws an error', async () => { + const token = tokenize({ + parameters: { userKey: 123, userName: 'john.doe@istio.actually.exists' }, + }) + + const response = await graphql({ + schema, + source: ` + mutation { + verifyAccount(input: { verifyTokenString: "${token}" }) { + result { + ... on VerifyAccountResult { + status + } + ... on VerifyAccountError { + code + description + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + request, + userKey: 123, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue({}), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error occurred.')), + abort: jest.fn(), + }), + auth: { + verifyToken: verifyToken({}), + }, + validators: { + cleanseInput, + }, + loaders: { + loadUserByKey: { + load: jest.fn().mockReturnValue({ + _key: 123, + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + tfaValidated: false, + emailValidated: false, + }), + }, + loadUserByUserName: loadUserByUserName({ query, userKey: 123, i18n }), + }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, + }, + }) + + const error = [new GraphQLError('Unable to verify account. Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when upserting email validation for user: 123: Error: Transaction commit error occurred.`, + ]) + }) + }) + }) + }) + }) +}) diff --git a/api-js/src/user/mutations/__tests__/verify-phone-number.test.js b/api/src/user/mutations/__tests__/verify-phone-number.test.js similarity index 82% rename from api-js/src/user/mutations/__tests__/verify-phone-number.test.js rename to api/src/user/mutations/__tests__/verify-phone-number.test.js index 841c3ef15e..4da0f61bfc 100644 --- a/api-js/src/user/mutations/__tests__/verify-phone-number.test.js +++ b/api/src/user/mutations/__tests__/verify-phone-number.test.js @@ -1,14 +1,16 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { loadUserByKey } from '../../loaders' import { userRequired } from '../../../auth' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -35,18 +37,21 @@ describe('user send password reset email', () => { describe('given a successful phone number verification', () => { beforeAll(async () => { ;({ query, drop, truncate, collections, transaction } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, tfaCode: 123456, @@ -74,9 +79,9 @@ describe('user send password reset email', () => { }) }) it('returns a successful status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -94,12 +99,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: userRequired({ @@ -111,7 +116,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -120,22 +125,19 @@ describe('user send password reset email', () => { user: { displayName: 'Test Account', }, - status: - 'Successfully verified phone number, and set TFA send method to text.', + status: 'Successfully verified phone number, and set TFA send method to text.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully two factor authenticated their account.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully two factor authenticated their account.`]) }) it('updates the user phoneValidated to true', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -153,12 +155,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: userRequired({ @@ -170,7 +172,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const cursor = await query` FOR user IN users @@ -180,9 +182,7 @@ describe('user send password reset email', () => { user = await cursor.next() expect(user.phoneValidated).toEqual(true) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully two factor authenticated their account.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully two factor authenticated their account.`]) }) }) describe('users language is set to french', () => { @@ -201,9 +201,9 @@ describe('user send password reset email', () => { }) }) it('returns a successful status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -221,12 +221,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: userRequired({ @@ -238,7 +238,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -255,14 +255,12 @@ describe('user send password reset email', () => { } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully two factor authenticated their account.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully two factor authenticated their account.`]) }) it('updates the user phoneValidated to true', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -280,12 +278,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: userRequired({ @@ -297,7 +295,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const cursor = await query` FOR user IN users @@ -307,9 +305,7 @@ describe('user send password reset email', () => { user = await cursor.next() expect(user.phoneValidated).toEqual(true) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully two factor authenticated their account.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully two factor authenticated their account.`]) }) }) }) @@ -331,9 +327,9 @@ describe('user send password reset email', () => { }) describe('the two factor code is not 6 digits long', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123 }) { result { @@ -351,12 +347,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: jest.fn().mockReturnValue({ @@ -369,15 +365,14 @@ describe('user send password reset email', () => { }, }, }, - ) + }) const error = { data: { verifyPhoneNumber: { result: { code: 400, - description: - 'Two factor code length is incorrect. Please try again.', + description: 'Two factor code length is incorrect. Please try again.', }, }, }, @@ -391,9 +386,9 @@ describe('user send password reset email', () => { }) describe('tfa codes do not match', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 654321 }) { result { @@ -411,12 +406,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: jest.fn().mockReturnValue({ @@ -430,15 +425,14 @@ describe('user send password reset email', () => { }, }, }, - ) + }) const error = { data: { verifyPhoneNumber: { result: { code: 400, - description: - 'Two factor code is incorrect. Please try again.', + description: 'Two factor code is incorrect. Please try again.', }, }, }, @@ -453,9 +447,9 @@ describe('user send password reset email', () => { describe('given a transaction step error', () => { describe('when upserting users phone validation status', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -473,16 +467,15 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), auth: { userRequired: jest.fn().mockReturnValue({ @@ -496,13 +489,9 @@ describe('user send password reset email', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to two factor authenticate. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to two factor authenticate. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -514,9 +503,9 @@ describe('user send password reset email', () => { describe('given a transaction commit error', () => { describe('when committing changes', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -534,17 +523,16 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), auth: { userRequired: jest.fn().mockReturnValue({ @@ -558,13 +546,9 @@ describe('user send password reset email', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Unable to two factor authenticate. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to two factor authenticate. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -591,9 +575,9 @@ describe('user send password reset email', () => { }) describe('the two factor code is not 6 digits long', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123 }) { result { @@ -611,12 +595,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: jest.fn().mockReturnValue({ @@ -629,15 +613,14 @@ describe('user send password reset email', () => { }, }, }, - ) + }) const error = { data: { verifyPhoneNumber: { result: { code: 400, - description: - 'La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.', + description: 'La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.', }, }, }, @@ -651,9 +634,9 @@ describe('user send password reset email', () => { }) describe('tfa codes do not match', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 654321 }) { result { @@ -671,12 +654,12 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction, auth: { userRequired: jest.fn().mockReturnValue({ @@ -690,15 +673,14 @@ describe('user send password reset email', () => { }, }, }, - ) + }) const error = { data: { verifyPhoneNumber: { result: { code: 400, - description: - 'Le code à deux facteurs est incorrect. Veuillez réessayer.', + description: 'Le code à deux facteurs est incorrect. Veuillez réessayer.', }, }, }, @@ -713,9 +695,9 @@ describe('user send password reset email', () => { describe('given a transaction step error', () => { describe('when upserting users phone validation status', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -733,16 +715,15 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Transaction step error')), + step: jest.fn().mockRejectedValue(new Error('Transaction step error')), + abort: jest.fn(), }), auth: { userRequired: jest.fn().mockReturnValue({ @@ -756,13 +737,9 @@ describe('user send password reset email', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de s'authentifier par deux facteurs. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de s'authentifier par deux facteurs. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -774,9 +751,9 @@ describe('user send password reset email', () => { describe('given a transaction commit error', () => { describe('when committing changes', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyPhoneNumber(input: { twoFactorCode: 123456 }) { result { @@ -794,17 +771,16 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, - collections, + collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue(new Error('Transaction commit error')), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error')), + abort: jest.fn(), }), auth: { userRequired: jest.fn().mockReturnValue({ @@ -818,13 +794,9 @@ describe('user send password reset email', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de s'authentifier par deux facteurs. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de s'authentifier par deux facteurs. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/user/mutations/authenticate.js b/api/src/user/mutations/authenticate.js new file mode 100644 index 0000000000..1761186ffa --- /dev/null +++ b/api/src/user/mutations/authenticate.js @@ -0,0 +1,234 @@ +import { GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' +import { authenticateUnion } from '../unions' +import { TfaSendMethodEnum } from '../../enums' +import ms from 'ms' + +const { REFRESH_KEY, REFRESH_TOKEN_EXPIRY, AUTHENTICATED_KEY, SIGN_IN_KEY, AUTH_TOKEN_EXPIRY } = process.env + +export const authenticate = new mutationWithClientMutationId({ + name: 'Authenticate', + description: + 'This mutation allows users to give their credentials and retrieve a token that gives them access to restricted content.', + inputFields: () => ({ + sendMethod: { + type: new GraphQLNonNull(TfaSendMethodEnum), + description: 'The method that the user wants to receive their authentication code by.', + }, + authenticationCode: { + type: new GraphQLNonNull(GraphQLInt), + description: 'Security code found in text msg, or email inbox.', + }, + authenticateToken: { + type: new GraphQLNonNull(GraphQLString), + description: 'The JWT that is retrieved from the sign in mutation.', + }, + }), + outputFields: () => ({ + result: { + type: authenticateUnion, + description: 'Authenticate union returning either a `authResult` or `authenticateError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + response, + query, + collections, + transaction, + uuidv4, + jwt, + auth: { tokenize, verifyToken }, + loaders: { loadUserByKey }, + validators: { cleanseInput }, + }, + ) => { + // Cleanse Inputs + const authenticationCode = args.authenticationCode + const authenticationToken = cleanseInput(args.authenticateToken) + const sendMethod = cleanseInput(args.sendMethod) + + // Gather token parameters + const tokenParameters = verifyToken({ + token: authenticationToken, + secret: String(SIGN_IN_KEY), + }) + + if (tokenParameters.userKey === 'undefined' || typeof tokenParameters.userKey === 'undefined') { + console.warn(`Authentication token does not contain the userKey`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Token value incorrect, please sign in again.`), + } + } + + // Gather sign in user + const user = await loadUserByKey.load(tokenParameters.userKey) + + // Replace with userRequired() + if (typeof user === 'undefined') { + console.warn(`User: ${tokenParameters.userKey} attempted to authenticate, no account is associated with this id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to authenticate. Please try again.`), + } + } + + // Check to see if security token matches the user submitted one + if (authenticationCode === user.tfaCode) { + const refreshId = uuidv4() + const loginDate = new Date().toISOString() + + const refreshInfo = { + refreshId, + rememberMe: user.refreshInfo.rememberMe, + expiresAt: new Date(new Date().getTime() + ms(REFRESH_TOKEN_EXPIRY)), + } + + // Setup Transaction + const trx = await transaction(collections) + + // Reset tfa code attempts, and set refresh code + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + tfaCode: null, + refreshInfo: ${refreshInfo}, + lastLogin: ${loginDate} + } + UPDATE { + tfaCode: null, + refreshInfo: ${refreshInfo}, + lastLogin: ${loginDate} + } + IN users + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when clearing tfa code and setting refresh id for user: ${user._key} during authentication: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to authenticate. Please try again.`)) + } + + // verify user email + if (sendMethod === 'email' && !user.emailValidated) { + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + emailValidated: true, + } + UPDATE { + emailValidated: true, + } + IN users + `, + ) + user.emailValidated = true + } catch (err) { + console.error( + `Trx step error occurred when setting email validated to true for user: ${user._key} during authentication: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to authenticate. Please try again.`)) + } + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${user._key} attempted to authenticate: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to authenticate. Please try again.`)) + } + + const token = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(AUTHENTICATED_KEY), + }) + + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: refreshId }, + secret: String(REFRESH_KEY), + }) + + // if the user does not want to stay logged in, create http session cookie + let cookieData = { + httpOnly: true, + secure: true, + sameSite: true, + expires: 0, + } + + // if user wants to stay logged in create normal http cookie + if (user.refreshInfo.rememberMe) { + const tokenMaxAgeSeconds = jwt.decode(refreshToken).exp - jwt.decode(refreshToken).iat + cookieData = { + maxAge: tokenMaxAgeSeconds * 1000, + httpOnly: true, + secure: true, + sameSite: true, + } + } + + response.cookie('refresh_token', refreshToken, cookieData) + + console.info(`User: ${user._key} successfully authenticated their account.`) + + return { + _type: 'authResult', + token, + user, + } + } else { + console.warn(`User: ${user._key} attempted to authenticate their account, however the tfaCodes did not match.`) + // reset tfa code + const trx = await transaction(collections) + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + tfaCode: null, + } + UPDATE { + tfaCode: null, + } + IN users + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when clearing tfa code on attempt timeout for user: ${user._key} during authentication: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Incorrect TFA code. Please sign in again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${user._key} attempted to authenticate: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Incorrect TFA code. Please sign in again.`)) + } + throw new Error(i18n._(t`Incorrect TFA code. Please sign in again.`)) + } + }, +}) diff --git a/api/src/user/mutations/close-account.js b/api/src/user/mutations/close-account.js new file mode 100644 index 0000000000..7a03baa586 --- /dev/null +++ b/api/src/user/mutations/close-account.js @@ -0,0 +1,234 @@ +import { t } from '@lingui/macro' +import { GraphQLID } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' +import { logActivity } from '../../audit-logs/mutations/log-activity' + +import { closeAccountUnion } from '../unions' + +export const closeAccountSelf = new mutationWithClientMutationId({ + name: 'CloseAccountSelf', + description: `This mutation allows a user to close their account.`, + outputFields: () => ({ + result: { + type: closeAccountUnion, + description: '`CloseAccountUnion` returning either a `CloseAccountResult`, or `CloseAccountError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { i18n, query, collections, transaction, request: { ip }, auth: { userRequired }, validators: { cleanseInput } }, + ) => { + let submittedUserId + if (args?.userId) { + submittedUserId = fromGlobalId(cleanseInput(args.userId)).id + } + + const user = await userRequired() + + const userId = user._id + const targetUserName = user.userName + + // Setup Trans action + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 INBOUND ${userId} affiliations + REMOVE { _key: e._key } IN affiliations + OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing users remaining affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to close account. Please try again.`)) + } + + try { + await trx.step( + () => query` + WITH users + REMOVE PARSE_IDENTIFIER(${userId}).key + IN users OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing user: ${user._key} attempted to close account: ${userId}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to close account. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when user: ${user._key} attempted to close account: ${userId}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to close account. Please try again.`)) + } + + console.info(`User: ${user._key} successfully closed user: ${userId} account.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: submittedUserId ? 'SUPER_ADMIN' : '', + ipAddress: ip, + }, + action: 'delete', + target: { + resource: targetUserName, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully closed account.`), + } + }, +}) + +export const closeAccountOther = new mutationWithClientMutationId({ + name: 'CloseAccountOther', + description: `This mutation allows a super admin to close another user's account.`, + inputFields: () => ({ + userId: { + type: GraphQLID, + description: 'The user id of a user you want to close the account of.', + }, + }), + outputFields: () => ({ + result: { + type: closeAccountUnion, + description: '`CloseAccountUnion` returning either a `CloseAccountResult`, or `CloseAccountError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + request: { ip }, + auth: { checkSuperAdmin, userRequired }, + loaders: { loadUserByKey }, + validators: { cleanseInput }, + }, + ) => { + let submittedUserId + if (args?.userId) { + submittedUserId = fromGlobalId(cleanseInput(args.userId)).id + } + + const user = await userRequired() + + let userId = '' + let targetUserName = '' + + const permission = await checkSuperAdmin() + if (!permission) { + console.warn( + `User: ${user._key} attempted to close user: ${submittedUserId} account, but requesting user is not a super admin.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission error: Unable to close other user's account.`), + } + } + + const checkUser = await loadUserByKey.load(submittedUserId) + if (typeof checkUser === 'undefined') { + console.warn( + `User: ${user._key} attempted to close user: ${submittedUserId} account, but requested user is undefined.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to close account of an undefined user.`), + } + } + userId = checkUser._id + targetUserName = checkUser.userName + + // Setup Trans action + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 INBOUND ${userId} affiliations + REMOVE { _key: e._key } IN affiliations + OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing users remaining affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to close account. Please try again.`)) + } + + try { + await trx.step( + () => query` + WITH users + REMOVE PARSE_IDENTIFIER(${userId}).key + IN users OPTIONS { waitForSync: true } + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when removing user: ${user._key} attempted to close account: ${userId}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to close account. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when user: ${user._key} attempted to close account: ${userId}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to close account. Please try again.`)) + } + + console.info(`User: ${user._key} successfully closed user: ${userId} account.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: submittedUserId ? 'SUPER_ADMIN' : '', + ipAddress: ip, + }, + action: 'delete', + target: { + resource: targetUserName, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully closed account.`), + user: checkUser, + } + }, +}) diff --git a/api/src/user/mutations/complete-tour.js b/api/src/user/mutations/complete-tour.js new file mode 100644 index 0000000000..8fff7a9458 --- /dev/null +++ b/api/src/user/mutations/complete-tour.js @@ -0,0 +1,77 @@ +import { GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { completeTourUnion } from '../unions' + +export const completeTour = new mutationWithClientMutationId({ + name: 'CompleteTour', + description: + 'This mutation allows users to confirm that they have completed the tour. This mutation will update the user object in the database to reflect that the user has completed the tour.', + inputFields: () => ({ + tourId: { + type: GraphQLString, + description: 'The id of the tour that the user is confirming completion of.', + }, + }), + outputFields: () => ({ + result: { + type: completeTourUnion, + description: '`CompleteTourUnion` returning either a `CompleteTourResult`, or `CompleteTourError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { i18n, query, auth: { userRequired }, loaders: { loadUserByKey }, validators: { cleanseInput } }, + ) => { + // Cleanse Input + const tourId = cleanseInput(args.tourId) + + // Get user info from DB + const user = await userRequired() + + if (!tourId) { + console.warn(`User: ${user._key} did not provide a tour id when attempting to confirm completion of the tour.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to confirm completion of the tour. Please try again.`), + } + } + + // Complete tour + try { + const completeTourCursor = await query` + LET userCompleteTours = FIRST( + FOR user IN users + FILTER user._key == ${user._key} + LIMIT 1 + RETURN user.completedTours + ) + UPDATE { _key: ${user._key} } + WITH { + completedTours: APPEND( + userCompleteTours[* FILTER CURRENT.tourId != ${tourId}], + { tourId: ${tourId}, completedAt: DATE_ISO8601(DATE_NOW()) } + ) + } + IN users + ` + await completeTourCursor.next() + } catch (err) { + console.error(`Database error occurred when user: ${user._key} attempted to complete tour: ${tourId}: ${err}`) + throw new Error(i18n._(t`Unable to confirm completion of the tour. Please try again.`)) + } + + await loadUserByKey.clear(user._key) + const returnUser = await loadUserByKey.load(user._key) + + console.info(`User: ${user._key} has confirmed completion of tour: ${tourId}`) + return { + _type: 'success', + status: i18n._(t`Tour completion confirmed successfully`), + user: returnUser, + } + }, +}) diff --git a/api/src/user/mutations/dismiss-message.js b/api/src/user/mutations/dismiss-message.js new file mode 100644 index 0000000000..a5bdbc3871 --- /dev/null +++ b/api/src/user/mutations/dismiss-message.js @@ -0,0 +1,79 @@ +import { GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { dismissMessageUnion } from '../unions' + +export const dismissMessage = new mutationWithClientMutationId({ + name: 'DismissMessage', + description: + 'This mutation allows users to dismiss a message that is displayed to them when they log in. This mutation will update the user object in the database to reflect that the message has been dismissed.', + inputFields: () => ({ + messageId: { + type: GraphQLString, + description: 'The id of the message that the user is dismissing.', + }, + }), + outputFields: () => ({ + result: { + type: dismissMessageUnion, + description: '`DismissMessageUnion` returning either a `DismissMessageResult`, or `DismissMessageError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { i18n, query, auth: { userRequired }, loaders: { loadUserByKey }, validators: { cleanseInput } }, + ) => { + // Cleanse Input + const messageId = cleanseInput(args.messageId) + + // Get user info from DB + const user = await userRequired() + + if (!messageId) { + console.warn(`User: ${user._key} did not provide a message id when attempting to dismiss a message.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to dismiss message. Please try again.`), + } + } + + // Dismiss message + try { + const dismissMessageCursor = await query` + LET userDismissedMessages = FIRST( + FOR user IN users + FILTER user._key == ${user._key} + LIMIT 1 + RETURN user.dismissedMessages + ) + UPDATE { _key: ${user._key} } + WITH { + dismissedMessages: APPEND( + userDismissedMessages[* FILTER CURRENT.messageId != ${messageId}], + { messageId: ${messageId}, dismissedAt: DATE_ISO8601(DATE_NOW()) } + ) + } + IN users + ` + await dismissMessageCursor.next() + } catch (err) { + console.error( + `Database error occurred when user: ${user._key} attempted to dismiss message: ${messageId}: ${err}`, + ) + throw new Error(i18n._(t`Unable to dismiss message. Please try again.`)) + } + + await loadUserByKey.clear(user._key) + const returnUser = await loadUserByKey.load(user._key) + + console.info(`User: ${user._key} successfully dismissed message: ${messageId}`) + return { + _type: 'success', + status: i18n._(t`Message dismissed successfully`), + user: returnUser, + } + }, +}) diff --git a/api/src/user/mutations/index.js b/api/src/user/mutations/index.js new file mode 100644 index 0000000000..0a11011ec8 --- /dev/null +++ b/api/src/user/mutations/index.js @@ -0,0 +1,16 @@ +export * from './authenticate' +export * from './close-account' +export * from './complete-tour' +export * from './dismiss-message' +export * from './refresh-tokens' +export * from './remove-phone-number' +export * from './reset-password' +export * from './send-password-reset' +export * from './set-phone-number' +export * from './sign-in' +export * from './sign-out' +export * from './sign-up' +export * from './update-user-password' +export * from './update-user-profile' +export * from './verify-account' +export * from './verify-phone-number' diff --git a/api/src/user/mutations/refresh-tokens.js b/api/src/user/mutations/refresh-tokens.js new file mode 100644 index 0000000000..0028b753e5 --- /dev/null +++ b/api/src/user/mutations/refresh-tokens.js @@ -0,0 +1,175 @@ +import { t } from '@lingui/macro' +import { mutationWithClientMutationId } from 'graphql-relay' + +import { refreshTokensUnion } from '../unions' +import ms from 'ms' + +const { REFRESH_TOKEN_EXPIRY, REFRESH_KEY, AUTHENTICATED_KEY, AUTH_TOKEN_EXPIRY } = process.env + +export const refreshTokens = new mutationWithClientMutationId({ + name: 'RefreshTokens', + description: + 'This mutation allows users to give their current auth token, and refresh token, and receive a freshly updated auth token.', + outputFields: () => ({ + result: { + type: refreshTokensUnion, + description: 'Refresh tokens union returning either a `authResult` or `authenticateError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + _, + { + i18n, + response, + request, + query, + collections, + transaction, + uuidv4, + jwt, + moment, + auth: { tokenize }, + loaders: { loadUserByKey }, + }, + ) => { + // check uuid matches + let refreshToken + if ('refresh_token' in request.cookies) { + refreshToken = request.cookies.refresh_token + } + + if (typeof refreshToken === 'undefined') { + console.warn(`User attempted to refresh tokens without refresh_token set.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to refresh tokens, please sign in.`), + } + } + + let decodedRefreshToken + try { + decodedRefreshToken = jwt.verify(refreshToken, REFRESH_KEY) + } catch (err) { + console.warn(`User attempted to verify refresh token, however the token is invalid: ${err}`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to refresh tokens, please sign in.`), + } + } + + const { userKey, uuid } = decodedRefreshToken.parameters + + const user = await loadUserByKey.load(userKey) + + if (typeof user === 'undefined') { + console.warn(`User: ${userKey} attempted to refresh tokens with an invalid user id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to refresh tokens, please sign in.`), + } + } + + // check to see if refresh token is expired + const currentTime = moment().format() + const expiryTime = moment(user.refreshInfo.expiresAt).format() + + if (moment(currentTime).isAfter(expiryTime)) { + console.warn(`User: ${userKey} attempted to refresh tokens with an expired uuid.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to refresh tokens, please sign in.`), + } + } + + // check to see if token ids match + if (user.refreshInfo.refreshId !== uuid) { + console.warn(`User: ${userKey} attempted to refresh tokens with non matching uuids.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to refresh tokens, please sign in.`), + } + } + + const newRefreshId = uuidv4() + + const refreshInfo = { + refreshId: newRefreshId, + rememberMe: user.refreshInfo.rememberMe, + expiresAt: new Date(new Date().getTime() + ms(String(REFRESH_TOKEN_EXPIRY))), + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { refreshInfo: ${refreshInfo} } + UPDATE { refreshInfo: ${refreshInfo} } + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when attempting to refresh tokens for user: ${userKey}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to refresh tokens, please sign in.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${userKey} attempted to refresh tokens: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to refresh tokens, please sign in.`)) + } + + const newAuthToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey }, + secret: String(AUTHENTICATED_KEY), + }) + + console.info(`User: ${userKey} successfully refreshed their tokens.`) + + const newRefreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: newRefreshId }, + secret: String(REFRESH_KEY), + }) + + // if the user does not want to stay logged in, create http session cookie + let cookieData = { + httpOnly: true, + secure: true, + sameSite: true, + expires: 0, + } + + // if user wants to stay logged in create normal http cookie + if (user.refreshInfo.rememberMe) { + const tokenMaxAgeSeconds = jwt.decode(newRefreshToken).exp - jwt.decode(newRefreshToken).iat + cookieData = { + maxAge: tokenMaxAgeSeconds * 1000, + httpOnly: true, + secure: true, + sameSite: true, + } + } + + response.cookie('refresh_token', newRefreshToken, cookieData) + + return { + _type: 'authResult', + token: newAuthToken, + user, + } + }, +}) diff --git a/api/src/user/mutations/remove-phone-number.js b/api/src/user/mutations/remove-phone-number.js new file mode 100644 index 0000000000..b1d449b1a0 --- /dev/null +++ b/api/src/user/mutations/remove-phone-number.js @@ -0,0 +1,68 @@ +import { t } from '@lingui/macro' +import { mutationWithClientMutationId } from 'graphql-relay' + +import { removePhoneNumberUnion } from '../unions' + +export const removePhoneNumber = new mutationWithClientMutationId({ + name: 'RemovePhoneNumber', + description: 'This mutation allows for users to remove a phone number from their account.', + outputFields: () => ({ + result: { + type: removePhoneNumberUnion, + description: + '`RemovePhoneNumberUnion` returning either a `RemovePhoneNumberResult`, or `RemovePhoneNumberError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async (_args, { i18n, collections, query, transaction, auth: { userRequired } }) => { + // Get requesting user + const user = await userRequired() + + // Set TFA method to backup incase user gets logged out, so they're not locked out of their account + let tfaSendMethod = 'none' + if (user.emailValidated && user.tfaSendMethod !== 'none') { + tfaSendMethod = 'email' + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + phoneDetails: null, + phoneValidated: false, + tfaSendMethod: ${tfaSendMethod} + } + UPDATE { + phoneDetails: null, + phoneValidated: false, + tfaSendMethod: ${tfaSendMethod} + } + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred well removing phone number for user: ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to remove phone number. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred well removing phone number for user: ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to remove phone number. Please try again.`)) + } + + console.info(`User: ${user._key} successfully removed their phone number.`) + return { + _type: 'result', + status: i18n._(t`Phone number has been successfully removed.`), + } + }, +}) diff --git a/api/src/user/mutations/reset-password.js b/api/src/user/mutations/reset-password.js new file mode 100644 index 0000000000..8deb0bbb7b --- /dev/null +++ b/api/src/user/mutations/reset-password.js @@ -0,0 +1,143 @@ +import { GraphQLNonNull, GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { resetPasswordUnion } from '../unions' + +const { AUTHENTICATED_KEY } = process.env + +export const resetPassword = new mutationWithClientMutationId({ + name: 'ResetPassword', + description: 'This mutation allows the user to take the token they received in their email to reset their password.', + inputFields: () => ({ + password: { + type: new GraphQLNonNull(GraphQLString), + description: 'The users new password.', + }, + confirmPassword: { + type: new GraphQLNonNull(GraphQLString), + description: 'A confirmation password to confirm the new password.', + }, + resetToken: { + type: new GraphQLNonNull(GraphQLString), + description: 'The JWT found in the url, redirected from the email they received.', + }, + }), + outputFields: () => ({ + result: { + type: resetPasswordUnion, + description: '`ResetPasswordUnion` returning either a `ResetPasswordResult`, or `ResetPasswordError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + auth: { verifyToken, bcrypt }, + loaders: { loadUserByKey }, + validators: { cleanseInput }, + }, + ) => { + // Cleanse input + const password = cleanseInput(args.password) + const confirmPassword = cleanseInput(args.confirmPassword) + const resetToken = cleanseInput(args.resetToken) + + // Check if reset token is valid + const tokenParameters = verifyToken({ token: resetToken, secret: String(AUTHENTICATED_KEY) }) + + // Check to see if user id exists in token params !!! + if (tokenParameters.userKey === 'undefined' || typeof tokenParameters.userKey === 'undefined') { + console.warn( + `When resetting password user attempted to verify account, but userKey is not located in the token parameters.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Incorrect token value. Please request a new email.`), + } + } + + // Check if user exists + const user = await loadUserByKey.load(tokenParameters.userKey) + + // Replace with userRequired() + if (typeof user === 'undefined') { + console.warn( + `A user attempted to reset the password for ${tokenParameters.userKey}, however there is no associated account.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to reset password. Please try again.`), + } + } + + // Check to see if newly submitted passwords match + if (password !== confirmPassword) { + console.warn( + `User: ${user._key} attempted to reset their password, however the submitted passwords do not match.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`New passwords do not match.`), + } + } + + // Check to see if password meets GoC requirements + if (password.length < 12) { + console.warn( + `User: ${user._key} attempted to reset their password, however the submitted password is not long enough.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Password does not meet requirements.`), + } + } + + // Update users password in db + const hashedPassword = bcrypt.hashSync(password, 10) + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH users + FOR user IN users + UPDATE ${user._key} + WITH { + password: ${hashedPassword}, + failedLoginAttempts: 0 + } IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when user: ${user._key} attempted to reset their password: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to reset password. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${user._key} attempted to authenticate: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to reset password. Please try again.`)) + } + + console.info(`User: ${user._key} successfully reset their password.`) + + return { + _type: 'regular', + status: i18n._(t`Password was successfully reset.`), + } + }, +}) diff --git a/api/src/user/mutations/send-password-reset.js b/api/src/user/mutations/send-password-reset.js new file mode 100644 index 0000000000..5578b94147 --- /dev/null +++ b/api/src/user/mutations/send-password-reset.js @@ -0,0 +1,64 @@ +import { GraphQLNonNull, GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' + +const { AUTHENTICATED_KEY } = process.env + +export const sendPasswordResetLink = new mutationWithClientMutationId({ + name: 'SendPasswordResetLink', + description: + 'This mutation allows a user to provide their username and request that a password reset email be sent to their account with a reset token in a url.', + inputFields: () => ({ + userName: { + type: new GraphQLNonNull(GraphQLEmailAddress), + description: 'User name for the account you would like to receive a password reset link for.', + }, + }), + outputFields: () => ({ + status: { + type: GraphQLString, + description: 'Informs the user if the password reset email was sent successfully.', + resolve: async (payload) => { + return payload.status + }, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + request, + auth: { tokenize }, + validators: { cleanseInput }, + loaders: { loadUserByUserName }, + notify: { sendPasswordResetEmail }, + }, + ) => { + // Cleanse Input + const userName = cleanseInput(args.userName).toLowerCase() + + const user = await loadUserByUserName.load(userName) + + if (typeof user !== 'undefined') { + const token = tokenize({ + expiresIn: '1h', + parameters: { userKey: user._key, currentPassword: user.password }, + secret: String(AUTHENTICATED_KEY), + }) + const resetUrl = `https://${request.get('host')}/reset-password/${token}` + + await sendPasswordResetEmail({ user, resetUrl }) + + console.info(`User: ${user._key} successfully sent a password reset email.`) + } else { + console.warn( + `A user attempted to send a password reset email for ${userName} but no account is affiliated with this user name.`, + ) + } + + return { + status: i18n._(t`If an account with this username is found, a password reset link will be found in your inbox.`), + } + }, +}) diff --git a/api/src/user/mutations/set-phone-number.js b/api/src/user/mutations/set-phone-number.js new file mode 100644 index 0000000000..09f874d6e7 --- /dev/null +++ b/api/src/user/mutations/set-phone-number.js @@ -0,0 +1,121 @@ +import crypto from 'crypto' +import { GraphQLNonNull } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { GraphQLPhoneNumber } from 'graphql-scalars' +import { t } from '@lingui/macro' + +import { setPhoneNumberUnion } from '../unions' + +const { CIPHER_KEY } = process.env + +export const setPhoneNumber = new mutationWithClientMutationId({ + name: 'SetPhoneNumber', + description: + 'This mutation is used for setting a new phone number for a user, and sending a code for verifying the new number.', + inputFields: () => ({ + phoneNumber: { + type: new GraphQLNonNull(GraphQLPhoneNumber), + description: 'The phone number that the text message will be sent to.', + }, + }), + outputFields: () => ({ + result: { + type: setPhoneNumberUnion, + description: '`SetPhoneNumberUnion` returning either a `SetPhoneNumberResult`, or `SetPhoneNumberError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + auth: { userRequired }, + loaders: { loadUserByKey }, + validators: { cleanseInput }, + notify: { sendAuthTextMsg }, + }, + ) => { + // Cleanse input + const phoneNumber = cleanseInput(args.phoneNumber) + + // Get User From Db + let user = await userRequired() + + // Generate TFA code + const tfaCode = Math.floor(100000 + Math.random() * 900000) + + // Generate Phone Details + const phoneDetails = { + iv: crypto.randomBytes(12).toString('hex'), + } + + const cipher = crypto.createCipheriv('aes-256-ccm', String(CIPHER_KEY), Buffer.from(phoneDetails.iv, 'hex'), { + authTagLength: 16, + }) + let encrypted = cipher.update(phoneNumber, 'utf8', 'hex') + encrypted += cipher.final('hex') + + phoneDetails.phoneNumber = encrypted + phoneDetails.tag = cipher.getAuthTag().toString('hex') + + // Set TFA method to backup incase user gets logged out, so they're not locked out of their account + let tfaSendMethod = 'none' + if (user.emailValidated && user.tfaSendMethod !== 'none') { + tfaSendMethod = 'email' + } + + // Setup Transaction + const trx = await transaction(collections) + + // Insert TFA code into DB + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + tfaCode: ${tfaCode}, + phoneDetails: ${phoneDetails}, + phoneValidated: false, + tfaSendMethod: ${tfaSendMethod} + } + UPDATE { + tfaCode: ${tfaCode}, + phoneDetails: ${phoneDetails}, + phoneValidated: false, + tfaSendMethod: ${tfaSendMethod} + } + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred for user: ${user._key} when upserting phone number information: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to set phone number, please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred for user: ${user._key} when upserting phone number information: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to set phone number, please try again.`)) + } + + // Get newly updated user + await loadUserByKey.clear(user._key) + user = await loadUserByKey.load(user._key) + + await sendAuthTextMsg({ user }) + + console.info(`User: ${user._key} successfully set phone number.`) + return { + _type: 'regular', + user: user, + status: i18n._(t`Phone number has been successfully set, you will receive a verification text message shortly.`), + } + }, +}) diff --git a/api/src/user/mutations/sign-in.js b/api/src/user/mutations/sign-in.js new file mode 100644 index 0000000000..bf0efdcdda --- /dev/null +++ b/api/src/user/mutations/sign-in.js @@ -0,0 +1,290 @@ +import { GraphQLBoolean, GraphQLNonNull, GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' + +import { signInUnion } from '../../user' +import ms from 'ms' + +const { SIGN_IN_KEY, REFRESH_TOKEN_EXPIRY, REFRESH_KEY, AUTHENTICATED_KEY, AUTH_TOKEN_EXPIRY } = process.env + +export const signIn = new mutationWithClientMutationId({ + name: 'SignIn', + description: + 'This mutation allows users to give their credentials and either signed in, re-directed to the tfa auth page, or given an error.', + inputFields: () => ({ + userName: { + type: new GraphQLNonNull(GraphQLEmailAddress), + description: 'The email the user signed up with.', + }, + password: { + type: new GraphQLNonNull(GraphQLString), + description: 'The password the user signed up with', + }, + rememberMe: { + type: GraphQLBoolean, + defaultValue: false, + description: 'Whether or not the user wants to stay signed in after leaving the site.', + }, + }), + outputFields: () => ({ + result: { + type: signInUnion, + description: + '`SignInUnion` returning either a `regularSignInResult`, `tfaSignInResult`, or `signInError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + uuidv4, + response, + jwt, + auth: { tokenize, bcrypt }, + loaders: { loadUserByUserName }, + validators: { cleanseInput }, + notify: { sendAuthEmail, sendAuthTextMsg }, + }, + ) => { + // Cleanse input + const userName = cleanseInput(args.userName).toLowerCase() + const password = cleanseInput(args.password) + const rememberMe = args.rememberMe + + // Gather user who just signed in + let user = await loadUserByUserName.load(userName) + + // Replace with userRequired() + if (typeof user === 'undefined') { + console.warn(`User: ${userName} attempted to sign in, no account is associated with this email.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Incorrect username or password. Please try again.`), + } + } + + // Check against failed attempt info + if (user.failedLoginAttempts >= 10) { + console.warn(`User: ${user._key} tried to sign in, but has too many login attempts.`) + return { + _type: 'error', + code: 401, + description: i18n._(t`Too many failed login attempts, please reset your password, and try again.`), + } + } else { + // Setup Transaction + const trx = await transaction(collections) + + // Check to see if passwords match + if (bcrypt.compareSync(password, user.password)) { + // Reset Failed Login attempts + try { + await trx.step( + () => query` + WITH users + FOR u IN users + UPDATE ${user._key} WITH { failedLoginAttempts: 0 } IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when resetting failed login attempts for user: ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + const refreshId = uuidv4() + const refreshInfo = { + refreshId, + expiresAt: new Date(new Date().getTime() + ms(String(REFRESH_TOKEN_EXPIRY))), + rememberMe, + } + + if (user.tfaSendMethod !== 'none') { + // Generate TFA code + const tfaCode = Math.floor(100000 + Math.random() * 900000) + + // Insert TFA code into DB + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + tfaCode: ${tfaCode}, + refreshInfo: ${refreshInfo} + } + UPDATE { + tfaCode: ${tfaCode}, + refreshInfo: ${refreshInfo} + } + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when inserting TFA code for user: ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${user._key} attempted to tfa sign in: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + // Get newly updated user + await loadUserByUserName.clear(userName) + user = await loadUserByUserName.load(userName) + + // Check if user's last successful login was over 30 days ago + let lastLogin + if (user.lastLogin) { + lastLogin = new Date(user.lastLogin) + } else { + lastLogin = new Date() + } + const currentDate = new Date() + const timeDifference = currentDate - lastLogin + const daysDifference = timeDifference / (1000 * 3600 * 24) + + // Check to see if user has phone validated + let sendMethod + if (user.tfaSendMethod === 'email' || daysDifference >= 30) { + await sendAuthEmail({ user }) + sendMethod = 'email' + } else { + await sendAuthTextMsg({ user }) + sendMethod = 'text' + } + + console.info(`User: ${user._key} successfully signed in, and sent auth msg.`) + + const authenticateToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(SIGN_IN_KEY), // SIGN_IN_KEY is reserved for signing TFA tokens + }) + + return { + _type: 'tfa', + sendMethod, + authenticateToken, + } + } else { + const loginDate = new Date().toISOString() + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { refreshInfo: ${refreshInfo}, lastLogin: ${loginDate} } + UPDATE { refreshInfo: ${refreshInfo}, lastLogin: ${loginDate} } + IN users + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when attempting to setting refresh tokens for user: ${user._key} during sign in: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${user._key} attempted a regular sign in: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + const token = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: user._key }, + secret: String(AUTHENTICATED_KEY), + }) + + const refreshToken = tokenize({ + expiresIn: REFRESH_TOKEN_EXPIRY, + parameters: { userKey: user._key, uuid: refreshId }, + secret: String(REFRESH_KEY), + }) + + // if the user does not want to stay logged in, create http session cookie + let cookieData = { + httpOnly: true, + secure: true, + sameSite: true, + expires: 0, + } + + // if user wants to stay logged in create normal http cookie + if (rememberMe) { + const tokenMaxAgeSeconds = jwt.decode(refreshToken).exp - jwt.decode(refreshToken).iat + cookieData = { + maxAge: tokenMaxAgeSeconds * 1000, + httpOnly: true, + secure: true, + sameSite: true, + } + } + + response.cookie('refresh_token', refreshToken, cookieData) + + console.info(`User: ${user._key} successfully signed in, and sent auth msg.`) + + return { + _type: 'regular', + token, + user, + } + } + } else { + // increment failed login attempts + user.failedLoginAttempts += 1 + + try { + // Increase users failed login attempts + await trx.step( + () => query` + WITH users + FOR u IN users + UPDATE ${user._key} WITH { + failedLoginAttempts: ${user.failedLoginAttempts} + } IN users + `, + ) + } catch (err) { + console.error( + `Trx step error occurred when incrementing failed login attempts for user: ${user._key}: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred while user: ${user._key} failed to sign in: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign in, please try again.`)) + } + + console.warn(`User attempted to authenticate: ${user._key} with invalid credentials.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Incorrect username or password. Please try again.`), + } + } + } + }, +}) diff --git a/api/src/user/mutations/sign-out.js b/api/src/user/mutations/sign-out.js new file mode 100644 index 0000000000..29e5b032d2 --- /dev/null +++ b/api/src/user/mutations/sign-out.js @@ -0,0 +1,28 @@ +import {t} from '@lingui/macro' +import {GraphQLString} from 'graphql' +import {mutationWithClientMutationId} from 'graphql-relay' + +export const signOut = new mutationWithClientMutationId({ + name: 'SignOut', + description: + 'This mutation allows a user to sign out, and clear their cookies.', + outputFields: () => ({ + status: { + type: GraphQLString, + description: 'Status of the users signing-out.', + resolve: ({status}) => status, + }, + }), + mutateAndGetPayload: async (_, {i18n, response}) => { + response.cookie('refresh_token', '', { + httpOnly: true, + expires: new Date(0), + secure: true, + sameSite: true, + }) + + return { + status: i18n._(t`Successfully signed out.`), + } + }, +}) diff --git a/api/src/user/mutations/sign-up.js b/api/src/user/mutations/sign-up.js new file mode 100644 index 0000000000..16dfb0eed7 --- /dev/null +++ b/api/src/user/mutations/sign-up.js @@ -0,0 +1,272 @@ +import { GraphQLBoolean, GraphQLNonNull, GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' +import { GraphQLEmailAddress } from 'graphql-scalars' + +import { signUpUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import ms from 'ms' +import { emailUpdateOptionsType } from '../objects/email-update-options' + +const { REFRESH_TOKEN_EXPIRY, SIGN_IN_KEY, AUTH_TOKEN_EXPIRY, TRACKER_PRODUCTION } = process.env + +export const signUp = new mutationWithClientMutationId({ + name: 'SignUp', + description: 'This mutation allows for new users to sign up for our sites services.', + inputFields: () => ({ + displayName: { + type: new GraphQLNonNull(GraphQLString), + description: 'The name that will be displayed to other users.', + }, + userName: { + type: new GraphQLNonNull(GraphQLEmailAddress), + description: 'Email address that the user will use to authenticate with.', + }, + password: { + type: new GraphQLNonNull(GraphQLString), + description: 'The password the user will authenticate with.', + }, + confirmPassword: { + type: new GraphQLNonNull(GraphQLString), + description: 'A secondary password field used to confirm the user entered the correct password.', + }, + signUpToken: { + type: GraphQLString, + description: 'A token sent by email, that will assign a user to an organization with a pre-determined role.', + }, + rememberMe: { + type: GraphQLBoolean, + defaultValue: false, + description: 'Whether or not the user wants to stay signed in after leaving the site.', + }, + }), + outputFields: () => ({ + result: { + type: signUpUnion, + description: '`SignUpUnion` returning either a `TFASignInResult`, or `SignUpError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + collections, + query, + transaction, + uuidv4, + request: { ip }, + auth: { bcrypt, tokenize, verifyToken }, + loaders: { loadOrgByKey, loadUserByUserName, loadUserByKey }, + notify: { sendAuthEmail }, + validators: { cleanseInput }, + }, + ) => { + // Cleanse Inputs + const displayName = cleanseInput(args.displayName) + const userName = cleanseInput(args.userName).toLowerCase() + const password = cleanseInput(args.password) + const confirmPassword = cleanseInput(args.confirmPassword) + const signUpToken = cleanseInput(args.signUpToken) + const rememberMe = args.rememberMe + + const isProduction = TRACKER_PRODUCTION === 'true' + if (isProduction === false) { + console.warn(`User: ${userName} tried to sign up but did not meet requirements.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`User is trying to register for a non-production environment.`), + } + } + + // Check to make sure password meets length requirement + if (password.length < 12) { + console.warn(`User: ${userName} tried to sign up but did not meet requirements.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Password does not meet requirements.`), + } + } + + // Check that password and password confirmation match + if (password !== confirmPassword) { + console.warn(`User: ${userName} tried to sign up but passwords do not match.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Passwords do not match.`), + } + } + + // Check to see if user already exists + const checkUser = await loadUserByUserName.load(userName) + + if (typeof checkUser !== 'undefined') { + console.warn(`User: ${userName} tried to sign up, however there is already an account in use with that email.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Email already in use.`), + } + } + + // Hash Users Password + const hashedPassword = bcrypt.hashSync(password, 10) + + const refreshId = uuidv4() + const tfaCode = Math.floor(100000 + Math.random() * 900000) + + // dynamically grabs email sub options + const emailUpdateOptions = Object.fromEntries( + Object.keys(emailUpdateOptionsType.getFields()).map((option) => [option, true]), + ) + + // Create User Structure for insert + const user = { + displayName: displayName, + userName: userName, + password: hashedPassword, + phoneValidated: false, + emailValidated: false, + insideUser: false, + emailUpdateOptions, + failedLoginAttempts: 0, + tfaSendMethod: 'email', + tfaCode: tfaCode, + refreshInfo: { + refreshId, + rememberMe, + expiresAt: new Date(new Date().getTime() + ms(String(REFRESH_TOKEN_EXPIRY))), + }, + } + + // Setup Transaction + const trx = await transaction(collections) + + let insertedUserCursor + try { + insertedUserCursor = await trx.step( + () => query` + WITH users + INSERT ${user} INTO users + RETURN MERGE( + { + id: NEW._key, + _type: "user" + }, + NEW + ) + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred while user: ${userName} attempted to sign up, creating user: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to sign up. Please try again.`)) + } + + let insertedUser + try { + insertedUser = await insertedUserCursor.next() + } catch (err) { + console.error(`Cursor error occurred while user: ${userName} attempted to sign up, creating user: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign up. Please try again.`)) + } + + // Assign user to org + if (signUpToken !== '') { + // Gather token parameters + const tokenParameters = verifyToken({ + token: signUpToken, + }) + + const tokenUserName = cleanseInput(tokenParameters.userName) + const tokenOrgKey = cleanseInput(tokenParameters.orgKey) + const tokenRequestedRole = cleanseInput(tokenParameters.requestedRole) + + if (userName !== tokenUserName) { + console.warn(`User: ${userName} attempted to sign up with an invite token, however emails do not match.`) + await trx.abort() + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to sign up, please contact org admin for a new invite.`), + } + } + + const checkOrg = await loadOrgByKey.load(tokenOrgKey) + if (typeof checkOrg === 'undefined') { + console.warn(`User: ${userName} attempted to sign up with an invite token, however the org could not be found.`) + await trx.abort() + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to sign up, please contact org admin for a new invite.`), + } + } + + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + INSERT { + _from: ${checkOrg._id}, + _to: ${insertedUser._id}, + permission: ${tokenRequestedRole}, + } INTO affiliations + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred while user: ${userName} attempted to sign up, assigning affiliation: ${err}`, + ) + await trx.abort() + throw new Error(i18n._(t`Unable to sign up. Please try again.`)) + } + } + + try { + await trx.commit() + } catch (err) { + console.error(`Transaction commit error occurred while user: ${userName} attempted to sign up: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to sign up. Please try again.`)) + } + + const returnUser = await loadUserByKey.load(insertedUser._key) + await sendAuthEmail({ user: returnUser }) + + const authenticateToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: insertedUser._key }, + secret: String(SIGN_IN_KEY), + }) + + console.info(`User: ${userName} successfully created a new account, and sent auth msg.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + userName, + ipAddress: ip, + }, + action: 'create', + target: { + resource: userName, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'tfa', + sendMethod: 'email', + authenticateToken, + } + }, +}) diff --git a/api/src/user/mutations/update-user-password.js b/api/src/user/mutations/update-user-password.js new file mode 100644 index 0000000000..78d07d6b6b --- /dev/null +++ b/api/src/user/mutations/update-user-password.js @@ -0,0 +1,112 @@ +import { GraphQLString, GraphQLNonNull } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { updateUserPasswordUnion } from '../unions' + +export const updateUserPassword = new mutationWithClientMutationId({ + name: 'UpdateUserPassword', + description: 'This mutation allows the user to update their account password.', + inputFields: () => ({ + currentPassword: { + type: new GraphQLNonNull(GraphQLString), + description: 'The users current password to verify it is the current user.', + }, + updatedPassword: { + type: new GraphQLNonNull(GraphQLString), + description: 'The new password the user wishes to change to.', + }, + updatedPasswordConfirm: { + type: new GraphQLNonNull(GraphQLString), + description: 'A password confirmation of their new password.', + }, + }), + outputFields: () => ({ + result: { + type: updateUserPasswordUnion, + description: + '`UpdateUserPasswordUnion` returning either a `UpdateUserPasswordResultType`, or `UpdateUserPasswordError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { i18n, query, collections, transaction, auth: { bcrypt, userRequired }, validators: { cleanseInput } }, + ) => { + // Cleanse Input + const currentPassword = cleanseInput(args.currentPassword) + const updatedPassword = cleanseInput(args.updatedPassword) + const updatedPasswordConfirm = cleanseInput(args.updatedPasswordConfirm) + + // Get user from db + const user = await userRequired() + + // Check to see if current passwords match + if (!bcrypt.compareSync(currentPassword, user.password)) { + console.warn( + `User: ${user._key} attempted to update their password, however they did not enter the current password correctly.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update password, current password does not match. Please try again.`), + } + } + + // Check to see if new passwords match + if (updatedPassword !== updatedPasswordConfirm) { + console.warn(`User: ${user._key} attempted to update their password, however the new passwords do not match.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update password, new passwords do not match. Please try again.`), + } + } + + // Check to see if they meet GoC requirements + if (updatedPassword.length < 12) { + console.warn( + `User: ${user._key} attempted to update their password, however the new password does not meet GoC requirements.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to update password, passwords do not match requirements. Please try again.`), + } + } + + // Update password in DB + const hashedPassword = bcrypt.hashSync(updatedPassword, 10) + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH users + FOR user IN users + UPDATE ${user._key} WITH { password: ${hashedPassword} } IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when user: ${user._key} attempted to update their password: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to update password. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when user: ${user._key} attempted to update their password: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to update password. Please try again.`)) + } + + console.info(`User: ${user._key} successfully updated their password.`) + return { + _type: 'regular', + status: i18n._(t`Password was successfully updated.`), + } + }, +}) diff --git a/api/src/user/mutations/update-user-profile.js b/api/src/user/mutations/update-user-profile.js new file mode 100644 index 0000000000..b7619fc81f --- /dev/null +++ b/api/src/user/mutations/update-user-profile.js @@ -0,0 +1,192 @@ +import { GraphQLString, GraphQLBoolean } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' + +import { TfaSendMethodEnum } from '../../enums' +import { updateUserProfileUnion } from '../unions' +import { emailUpdatesInput } from '../inputs/email-update-options' + +const { AUTHENTICATED_KEY, AUTH_TOKEN_EXPIRY } = process.env + +export const updateUserProfile = new mutationWithClientMutationId({ + name: 'UpdateUserProfile', + description: + 'This mutation allows the user to update their user profile to change various details of their current profile.', + inputFields: () => ({ + displayName: { + type: GraphQLString, + description: 'The updated display name the user wishes to change to.', + }, + userName: { + type: GraphQLEmailAddress, + description: 'The updated user name the user wishes to change to.', + }, + tfaSendMethod: { + type: TfaSendMethodEnum, + description: 'The method in which the user wishes to have their TFA code sent via.', + }, + insideUser: { + type: GraphQLBoolean, + description: 'The updated boolean which represents if the user wants to see features in progress.', + }, + emailUpdateOptions: { + type: emailUpdatesInput, + description: + 'A number of different emails the user can optionally receive periodically that provide updates about their organization.', + }, + }), + outputFields: () => ({ + result: { + type: updateUserProfileUnion, + description: + '`UpdateUserProfileUnion` returning either a `UpdateUserProfileResult`, or `UpdateUserProfileError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + userKey, + request, + auth: { tokenize, userRequired }, + loaders: { loadUserByKey, loadUserByUserName }, + notify: { sendVerificationEmail }, + validators: { cleanseInput }, + }, + ) => { + // Cleanse Input + const displayName = cleanseInput(args.displayName) + const userName = cleanseInput(args.userName).toLowerCase() + const subTfaSendMethod = cleanseInput(args.tfaSendMethod) + const insideUserBool = args.insideUser + const emailUpdateOptions = args.emailUpdateOptions + + // Get user info from DB + const user = await userRequired() + + // Check to see if username is already in use + if (userName !== '') { + const checkUser = await loadUserByUserName.load(userName) + if (typeof checkUser !== 'undefined') { + console.warn(`User: ${userKey} attempted to update their username, but the username is already in use.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Username not available, please try another.`), + } + } + } + + // Check to see if admin user is disabling TFA + if (subTfaSendMethod === 'none') { + // check to see if user is an admin or higher for at least one org + let userAdmin + try { + userAdmin = await query` + WITH users, affiliations + FOR v, e IN 1..1 INBOUND ${user._id} affiliations + FILTER e.permission IN ["admin", "owner", "super_admin"] + LIMIT 1 + RETURN e.permission + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} was seeing if they were an admin, err: ${err}`) + throw new Error(i18n._(t`Unable to verify if user is an admin, please try again.`)) + } + + if (userAdmin.count > 0) { + console.error( + `User: ${userKey} attempted to remove MFA, however they are an admin of at least one organization.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Multi-factor authentication is required for admin accounts`), + } + } + } + + let tfaSendMethod + if (subTfaSendMethod === 'phone' && user.phoneValidated) { + tfaSendMethod = 'phone' + } else if (subTfaSendMethod === 'email' && user.emailValidated) { + tfaSendMethod = 'email' + } else if (subTfaSendMethod === 'none' || typeof user.tfaSendMethod === 'undefined') { + tfaSendMethod = 'none' + } else { + tfaSendMethod = user.tfaSendMethod + } + + let changedUserName = false + if (userName !== user.userName && userName !== '') { + changedUserName = true + } + + // Create object containing updated data expect username. Username is handled separately for verification. + const updatedUser = { + displayName: displayName || user.displayName, + tfaSendMethod: tfaSendMethod, + insideUser: typeof insideUserBool !== 'undefined' ? insideUserBool : user?.insideUser, + emailUpdateOptions: typeof emailUpdateOptions !== 'undefined' ? emailUpdateOptions : user?.emailUpdateOptions, + } + + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT ${updatedUser} + UPDATE ${updatedUser} + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when user: ${userKey} attempted to update their profile: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to update profile. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when user: ${userKey} attempted to update their profile: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to update profile. Please try again.`)) + } + + await loadUserByKey.clear(user._key) + const returnUser = await loadUserByKey.load(userKey) + + if (changedUserName) { + const token = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: returnUser._key, userName: userName }, + secret: String(AUTHENTICATED_KEY), + }) + + const verifyUrl = `https://${request.get('host')}/validate/${token}` + + await sendVerificationEmail({ + userName: userName, + displayName: returnUser.displayName, + verifyUrl: verifyUrl, + userKey: returnUser._key, + }) + } + + console.info(`User: ${userKey} successfully updated their profile.`) + return { + _type: 'success', + status: i18n._(t`Profile successfully updated.`), + user: returnUser, + } + }, +}) diff --git a/api/src/user/mutations/verify-account.js b/api/src/user/mutations/verify-account.js new file mode 100644 index 0000000000..19fa3a894c --- /dev/null +++ b/api/src/user/mutations/verify-account.js @@ -0,0 +1,146 @@ +import { GraphQLNonNull, GraphQLString } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { verifyAccountUnion } from '../unions' + +export const verifyAccount = new mutationWithClientMutationId({ + name: 'VerifyAccount', + description: + 'This mutation allows the user to switch usernames/verify their account through a token sent in an email.', + inputFields: () => ({ + verifyTokenString: { + type: new GraphQLNonNull(GraphQLString), + description: 'Token sent via email, and located in url.', + }, + }), + outputFields: () => ({ + result: { + type: verifyAccountUnion, + description: '`VerifyAccountUnion` returning either a `VerifyAccountResult`, or `VerifyAccountError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + collections, + transaction, + auth: { verifyToken }, + loaders: { loadUserByKey, loadUserByUserName }, + notify: { sendUpdatedUserNameEmail }, + validators: { cleanseInput }, + }, + ) => { + // Cleanse Input + const verifyTokenString = cleanseInput(args.verifyTokenString) + + // Get info from token + const tokenParameters = verifyToken({ token: verifyTokenString }) + + // Check to see if userKey exists in tokenParameters + if (!tokenParameters?.userKey) { + console.warn( + `When validating account, user attempted to verify account, but userKey is not located in the token parameters.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to verify account. Please request a new email.`), + } + } + + // Check to see if userName exists in tokenParameters + if (!tokenParameters?.userName) { + console.warn( + `When validating account, user attempted to verify account, but userName is not located in the token parameters.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to verify account. Please request a new email.`), + } + } + + // Auth shouldn't be needed with this + // Check if user exists + const { userKey, userName: newUserName } = tokenParameters + const user = await loadUserByKey.load(userKey) + + if (typeof user === 'undefined') { + console.warn(`User: ${userKey} attempted to verify account, however no account is associated with this id.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to verify account. Please request a new email.`), + } + } + + // Ensure newUserName is still not already in use + const checkUser = await loadUserByUserName.load(newUserName) + if (typeof checkUser !== 'undefined') { + console.warn(`User: ${userKey} attempted to update their username, but the username is already in use.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Username not available, please try another.`), + } + } + + // Send email to current email address + try { + await sendUpdatedUserNameEmail({ + previousUserName: user.userName, + newUserName, + displayName: user.displayName, + userKey, + }) + } catch (err) { + console.error(`Error occurred when sending updated username email for ${userKey}: ${err}`) + throw new Error(i18n._(t`Unable to send updated username email. Please try again.`)) + } + + // Setup Transaction + const trx = await transaction(collections) + + // Verify users account + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { + emailValidated: true, + userName: ${newUserName}, + } + UPDATE { + emailValidated: true, + userName: ${newUserName}, + } + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when upserting email validation for user: ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to verify account. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when upserting email validation for user: ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to verify account. Please try again.`)) + } + + console.info(`User: ${user._key} successfully email validated their account.`) + + return { + _type: 'success', + status: i18n._(t`Successfully email verified account.`), + } + }, +}) diff --git a/api/src/user/mutations/verify-phone-number.js b/api/src/user/mutations/verify-phone-number.js new file mode 100644 index 0000000000..bcbf7d0fca --- /dev/null +++ b/api/src/user/mutations/verify-phone-number.js @@ -0,0 +1,94 @@ +import { GraphQLNonNull, GraphQLInt } from 'graphql' +import { mutationWithClientMutationId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { verifyPhoneNumberUnion } from '../unions' + +export const verifyPhoneNumber = new mutationWithClientMutationId({ + name: 'verifyPhoneNumber', + description: 'This mutation allows the user to two factor authenticate.', + inputFields: () => ({ + twoFactorCode: { + type: new GraphQLNonNull(GraphQLInt), + description: 'The two factor code that was received via text message.', + }, + }), + outputFields: () => ({ + result: { + type: verifyPhoneNumberUnion, + description: + '`VerifyPhoneNumberUnion` returning either a `VerifyPhoneNumberResult`, or `VerifyPhoneNumberError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { i18n, userKey, query, collections, transaction, auth: { userRequired }, loaders: { loadUserByKey } }, + ) => { + // Cleanse Input + const twoFactorCode = args.twoFactorCode + + // Get User From DB + const user = await userRequired() + + if (twoFactorCode.toString().length !== 6) { + console.warn( + `User: ${user._key} attempted to two factor authenticate, however the code they submitted does not have 6 digits.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Two factor code length is incorrect. Please try again.`), + } + } + + // Check that TFA codes match + if (twoFactorCode !== user.tfaCode) { + console.warn(`User: ${user._key} attempted to two factor authenticate, however the tfa codes do not match.`) + return { + _type: 'error', + code: 400, + description: i18n._(t`Two factor code is incorrect. Please try again.`), + } + } + + // Setup Transaction + const trx = await transaction(collections) + + // Update phoneValidated to be true + try { + await trx.step( + () => query` + WITH users + UPSERT { _key: ${user._key} } + INSERT { phoneValidated: true } + UPDATE { phoneValidated: true } + IN users + `, + ) + } catch (err) { + console.error(`Trx step error occurred when upserting the tfaValidate field for ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to two factor authenticate. Please try again.`)) + } + + try { + await trx.commit() + } catch (err) { + console.error(`Trx commit error occurred when upserting the tfaValidate field for ${user._key}: ${err}`) + await trx.abort() + throw new Error(i18n._(t`Unable to two factor authenticate. Please try again.`)) + } + + await loadUserByKey.clear(userKey) + const updatedUser = await loadUserByKey.load(userKey) + + console.info(`User: ${user._key} successfully two factor authenticated their account.`) + + return { + _type: 'success', + user: updatedUser, + status: i18n._(t`Successfully verified phone number, and set TFA send method to text.`), + } + }, +}) diff --git a/api-js/src/user/objects/__tests__/auth-result.test.js b/api/src/user/objects/__tests__/auth-result.test.js similarity index 93% rename from api-js/src/user/objects/__tests__/auth-result.test.js rename to api/src/user/objects/__tests__/auth-result.test.js index 4b2fdaa8d6..7ce3e2734b 100644 --- a/api-js/src/user/objects/__tests__/auth-result.test.js +++ b/api/src/user/objects/__tests__/auth-result.test.js @@ -23,9 +23,7 @@ describe('given the auth result gql object', () => { it('returns the resolved field', () => { const demoType = authResultType.getFields() - expect(demoType.authToken.resolve({ token: 'authToken' })).toEqual( - 'authToken', - ) + expect(demoType.authToken.resolve({ token: 'authToken' })).toEqual('authToken') }) }) describe('testing the user field', () => { @@ -35,7 +33,6 @@ describe('given the auth result gql object', () => { const user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, } @@ -43,7 +40,6 @@ describe('given the auth result gql object', () => { const expectedResult = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, } diff --git a/api-js/src/user/objects/__tests__/authenticate-error.test.js b/api/src/user/objects/__tests__/authenticate-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/authenticate-error.test.js rename to api/src/user/objects/__tests__/authenticate-error.test.js index 982043fbf3..84f1b58c8b 100644 --- a/api-js/src/user/objects/__tests__/authenticate-error.test.js +++ b/api/src/user/objects/__tests__/authenticate-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { authenticateError } from '../index' +import {authenticateError} from '../index' describe('given the authenticateError object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the authenticateError object', () => { it('returns the resolved field', () => { const demoType = authenticateError.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the authenticateError object', () => { const demoType = authenticateError.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/close-account-error.test.js b/api/src/user/objects/__tests__/close-account-error.test.js similarity index 79% rename from api-js/src/user/objects/__tests__/close-account-error.test.js rename to api/src/user/objects/__tests__/close-account-error.test.js index d75a0336ce..fe2f13bcdc 100644 --- a/api-js/src/user/objects/__tests__/close-account-error.test.js +++ b/api/src/user/objects/__tests__/close-account-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { closeAccountError } from '../close-account-error' +import {closeAccountError} from '../close-account-error' describe('given the closeAccountError object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the closeAccountError object', () => { it('returns the resolved field', () => { const demoType = closeAccountError.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the closeAccountError object', () => { const demoType = closeAccountError.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/close-account-result.test.js b/api/src/user/objects/__tests__/close-account-result.test.js similarity index 75% rename from api-js/src/user/objects/__tests__/close-account-result.test.js rename to api/src/user/objects/__tests__/close-account-result.test.js index a2f0d08035..59560abfe9 100644 --- a/api-js/src/user/objects/__tests__/close-account-result.test.js +++ b/api/src/user/objects/__tests__/close-account-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { closeAccountResult } from '../close-account-result' +import {closeAccountResult} from '../close-account-result' describe('given the closeAccountResult object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the closeAccountResult object', () => { it('returns the resolved field', () => { const demoType = closeAccountResult.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) }) diff --git a/api/src/user/objects/__tests__/complete-tour-error.test.js b/api/src/user/objects/__tests__/complete-tour-error.test.js new file mode 100644 index 0000000000..686731fe09 --- /dev/null +++ b/api/src/user/objects/__tests__/complete-tour-error.test.js @@ -0,0 +1,36 @@ +import { GraphQLInt, GraphQLString } from 'graphql' +import { completeTourError } from '../complete-tour-error' + +describe('given the completeTourError object', () => { + describe('testing the field definitions', () => { + it('has an code field', () => { + const demoType = completeTourError.getFields() + + expect(demoType).toHaveProperty('code') + expect(demoType.code.type).toMatchObject(GraphQLInt) + }) + it('has a description field', () => { + const demoType = completeTourError.getFields() + + expect(demoType).toHaveProperty('description') + expect(demoType.description.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the code resolver', () => { + it('returns the resolved field', () => { + const demoType = completeTourError.getFields() + + expect(demoType.code.resolve({ code: 400 })).toEqual(400) + }) + }) + describe('testing the description field', () => { + it('returns the resolved value', () => { + const demoType = completeTourError.getFields() + + expect(demoType.description.resolve({ description: 'description' })).toEqual('description') + }) + }) + }) +}) diff --git a/api/src/user/objects/__tests__/complete-tour-result.test.js b/api/src/user/objects/__tests__/complete-tour-result.test.js new file mode 100644 index 0000000000..d868fd048f --- /dev/null +++ b/api/src/user/objects/__tests__/complete-tour-result.test.js @@ -0,0 +1,49 @@ +import { GraphQLString } from 'graphql' + +import { userPersonalType } from '../user-personal' +import { completeTourResult } from '../complete-tour-result' + +describe('given the completeTourResult object', () => { + describe('testing the field definitions', () => { + it('has a status field', () => { + const demoType = completeTourResult.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + it('has a user field', () => { + const demoType = completeTourResult.getFields() + + expect(demoType).toHaveProperty('user') + expect(demoType.user.type).toMatchObject(userPersonalType) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = completeTourResult.getFields() + + expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + }) + }) + describe('testing the user resolver', () => { + it('returns the resolved field', () => { + const demoType = completeTourResult.getFields() + + const user = { + userName: 'user@test.com', + displayName: 'Test Account', + completedTours: [ + { + tourId: 'tour1', + completedAt: new Date(), + }, + ], + } + + expect(demoType.user.resolve({ user })).toEqual(user) + }) + }) + }) +}) diff --git a/api/src/user/objects/__tests__/dismiss-message-error.test.js b/api/src/user/objects/__tests__/dismiss-message-error.test.js new file mode 100644 index 0000000000..e1a6af6d86 --- /dev/null +++ b/api/src/user/objects/__tests__/dismiss-message-error.test.js @@ -0,0 +1,37 @@ +import { GraphQLInt, GraphQLString } from 'graphql' + +import { dismissMessageError } from '../dismiss-message-error' + +describe('given the dismissMessageError object', () => { + describe('testing the field definitions', () => { + it('has an code field', () => { + const demoType = dismissMessageError.getFields() + + expect(demoType).toHaveProperty('code') + expect(demoType.code.type).toMatchObject(GraphQLInt) + }) + it('has a description field', () => { + const demoType = dismissMessageError.getFields() + + expect(demoType).toHaveProperty('description') + expect(demoType.description.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the code resolver', () => { + it('returns the resolved field', () => { + const demoType = dismissMessageError.getFields() + + expect(demoType.code.resolve({ code: 400 })).toEqual(400) + }) + }) + describe('testing the description field', () => { + it('returns the resolved value', () => { + const demoType = dismissMessageError.getFields() + + expect(demoType.description.resolve({ description: 'description' })).toEqual('description') + }) + }) + }) +}) diff --git a/api/src/user/objects/__tests__/dismiss-message-result.test.js b/api/src/user/objects/__tests__/dismiss-message-result.test.js new file mode 100644 index 0000000000..870d53830f --- /dev/null +++ b/api/src/user/objects/__tests__/dismiss-message-result.test.js @@ -0,0 +1,49 @@ +import { GraphQLString } from 'graphql' + +import { dismissMessageResult } from '../dismiss-message-result' +import { userPersonalType } from '../user-personal' + +describe('given the dismissMessageResult object', () => { + describe('testing the field definitions', () => { + it('has a status field', () => { + const demoType = dismissMessageResult.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + it('has a user field', () => { + const demoType = dismissMessageResult.getFields() + + expect(demoType).toHaveProperty('user') + expect(demoType.user.type).toMatchObject(userPersonalType) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = dismissMessageResult.getFields() + + expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + }) + }) + describe('testing the user resolver', () => { + it('returns the resolved field', () => { + const demoType = dismissMessageResult.getFields() + + const user = { + userName: 'user@test.com', + displayName: 'Test Account', + dismissedMessage: [ + { + messageId: 'message1', + dismissedAt: new Date(), + }, + ], + } + + expect(demoType.user.resolve({ user })).toEqual(user) + }) + }) + }) +}) diff --git a/api/src/user/objects/__tests__/my-tracker-result.test.js b/api/src/user/objects/__tests__/my-tracker-result.test.js new file mode 100644 index 0000000000..5a76667f86 --- /dev/null +++ b/api/src/user/objects/__tests__/my-tracker-result.test.js @@ -0,0 +1,117 @@ +import { GraphQLInt } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { domainConnection } from '../../../domain/objects' +import { organizationSummaryType } from '../../../organization/objects' + +import { myTrackerType } from '../index' + +describe('given the myTracker result gql object', () => { + describe('testing field definitions', () => { + it('has a summaries field', () => { + const demoType = myTrackerType.getFields() + + expect(demoType).toHaveProperty('summaries') + expect(demoType.summaries.type).toMatchObject(organizationSummaryType) + }) + it('has a domainCount field', () => { + const demoType = myTrackerType.getFields() + + expect(demoType).toHaveProperty('domainCount') + expect(demoType.domainCount.type).toMatchObject(GraphQLInt) + }) + it('has a domains field', () => { + const demoType = myTrackerType.getFields() + + expect(demoType).toHaveProperty('domains') + expect(demoType.domains.type).toMatchObject( + domainConnection.connectionType, + ) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the summaries resolver', () => { + it('returns the resolved field', () => { + const demoType = myTrackerType.getFields() + + const org = { + summaries: { + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }, + } + + const expectedResult = { + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + } + + expect(demoType.summaries.resolve(org)).toEqual(expectedResult) + }) + }) + }) + describe('testing the domainCount field', () => { + it('returns the resolved value', () => { + const demoType = myTrackerType.getFields() + expect(demoType.domainCount.resolve({ domainCount: 5 })).toEqual(5) + }) + }) + describe('testing the domains resolver', () => { + it('returns the resolved value', async () => { + const demoType = myTrackerType.getFields() + + const expectedResult = { + edges: [ + { + cursor: toGlobalId('domains', '1'), + node: { + _id: 'domains/1', + _key: '1', + _rev: 'rev', + _type: 'domain', + id: '1', + domain: 'test.gc.ca', + }, + }, + ], + totalCount: 1, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('domains', '1'), + endCursor: toGlobalId('domains', '1'), + }, + } + + await expect( + demoType.domains.resolve( + { _id: 'users/1' }, + { first: 1 }, + { + loaders: { + loadDomainConnectionsByUserId: jest + .fn() + .mockReturnValue(expectedResult), + }, + }, + ), + ).resolves.toEqual(expectedResult) + }) + }) +}) diff --git a/api-js/src/user/objects/__tests__/remove-phone-number-error.test.js b/api/src/user/objects/__tests__/remove-phone-number-error.test.js similarity index 79% rename from api-js/src/user/objects/__tests__/remove-phone-number-error.test.js rename to api/src/user/objects/__tests__/remove-phone-number-error.test.js index 18e849643f..189da64535 100644 --- a/api-js/src/user/objects/__tests__/remove-phone-number-error.test.js +++ b/api/src/user/objects/__tests__/remove-phone-number-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { removePhoneNumberErrorType } from '../remove-phone-number-error' +import {removePhoneNumberErrorType} from '../remove-phone-number-error' describe('given the removePhoneNumberErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the removePhoneNumberErrorType object', () => { it('returns the resolved field', () => { const demoType = removePhoneNumberErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the removePhoneNumberErrorType object', () => { const demoType = removePhoneNumberErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api/src/user/objects/__tests__/remove-phone-number-result.test.js b/api/src/user/objects/__tests__/remove-phone-number-result.test.js new file mode 100644 index 0000000000..bdbd6558f0 --- /dev/null +++ b/api/src/user/objects/__tests__/remove-phone-number-result.test.js @@ -0,0 +1,24 @@ +import {GraphQLString} from 'graphql' + +import {removePhoneNumberResultType} from '../remove-phone-number-result' + +describe('given the removePhoneNumberResultType object', () => { + describe('testing the field definitions', () => { + it('has an status field', () => { + const demoType = removePhoneNumberResultType.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = removePhoneNumberResultType.getFields() + + expect(demoType.status.resolve({status: 'status'})).toEqual('status') + }) + }) + }) +}) diff --git a/api-js/src/user/objects/__tests__/reset-password-error.test.js b/api/src/user/objects/__tests__/reset-password-error.test.js similarity index 79% rename from api-js/src/user/objects/__tests__/reset-password-error.test.js rename to api/src/user/objects/__tests__/reset-password-error.test.js index 4e779680cf..a55f081463 100644 --- a/api-js/src/user/objects/__tests__/reset-password-error.test.js +++ b/api/src/user/objects/__tests__/reset-password-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { resetPasswordErrorType } from '../reset-password-error' +import {resetPasswordErrorType} from '../reset-password-error' describe('given the resetPasswordErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the resetPasswordErrorType object', () => { it('returns the resolved field', () => { const demoType = resetPasswordErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the resetPasswordErrorType object', () => { const demoType = resetPasswordErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/reset-password-result.test.js b/api/src/user/objects/__tests__/reset-password-result.test.js similarity index 75% rename from api-js/src/user/objects/__tests__/reset-password-result.test.js rename to api/src/user/objects/__tests__/reset-password-result.test.js index 351d768a0f..7165865254 100644 --- a/api-js/src/user/objects/__tests__/reset-password-result.test.js +++ b/api/src/user/objects/__tests__/reset-password-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { resetPasswordResultType } from '../reset-password-result' +import {resetPasswordResultType} from '../reset-password-result' describe('given the resetPasswordErrorType object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the resetPasswordErrorType object', () => { it('returns the resolved field', () => { const demoType = resetPasswordResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) }) diff --git a/api-js/src/user/objects/__tests__/set-phone-number-error.test.js b/api/src/user/objects/__tests__/set-phone-number-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/set-phone-number-error.test.js rename to api/src/user/objects/__tests__/set-phone-number-error.test.js index 2d04fa3d54..8ec7d21bc8 100644 --- a/api-js/src/user/objects/__tests__/set-phone-number-error.test.js +++ b/api/src/user/objects/__tests__/set-phone-number-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { setPhoneNumberErrorType } from '../index' +import {setPhoneNumberErrorType} from '../index' describe('given the setPhoneNumberErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the setPhoneNumberErrorType object', () => { it('returns the resolved field', () => { const demoType = setPhoneNumberErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the setPhoneNumberErrorType object', () => { const demoType = setPhoneNumberErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/set-phone-number-result.test.js b/api/src/user/objects/__tests__/set-phone-number-result.test.js similarity index 81% rename from api-js/src/user/objects/__tests__/set-phone-number-result.test.js rename to api/src/user/objects/__tests__/set-phone-number-result.test.js index 02cfc2156e..7c17fe4645 100644 --- a/api-js/src/user/objects/__tests__/set-phone-number-result.test.js +++ b/api/src/user/objects/__tests__/set-phone-number-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { setPhoneNumberResultType, userPersonalType } from '../index' +import {setPhoneNumberResultType, userPersonalType} from '../index' describe('given the setPhoneNumberResultType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the setPhoneNumberResultType object', () => { it('returns the resolved field', () => { const demoType = setPhoneNumberResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) describe('testing the user resolver', () => { @@ -34,7 +34,7 @@ describe('given the setPhoneNumberResultType object', () => { displayName: 'John Doe', } - expect(demoType.user.resolve({ user })).toEqual({ + expect(demoType.user.resolve({user})).toEqual({ displayName: 'John Doe', }) }) diff --git a/api-js/src/user/objects/__tests__/sign-in-error.test.js b/api/src/user/objects/__tests__/sign-in-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/sign-in-error.test.js rename to api/src/user/objects/__tests__/sign-in-error.test.js index a87015743e..817b8f3a10 100644 --- a/api-js/src/user/objects/__tests__/sign-in-error.test.js +++ b/api/src/user/objects/__tests__/sign-in-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { signInError } from '../index' +import {signInError} from '../index' describe('given the signInError object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the signInError object', () => { it('returns the resolved field', () => { const demoType = signInError.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the signInError object', () => { const demoType = signInError.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/sign-up-error.test.js b/api/src/user/objects/__tests__/sign-up-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/sign-up-error.test.js rename to api/src/user/objects/__tests__/sign-up-error.test.js index 150479b1ea..605ef0e4f9 100644 --- a/api-js/src/user/objects/__tests__/sign-up-error.test.js +++ b/api/src/user/objects/__tests__/sign-up-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { signUpError } from '../index' +import {signUpError} from '../index' describe('given the signUpError object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the signUpError object', () => { it('returns the resolved field', () => { const demoType = signUpError.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the signUpError object', () => { const demoType = signUpError.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/tfa-sign-in-result.test.js b/api/src/user/objects/__tests__/tfa-sign-in-result.test.js similarity index 82% rename from api-js/src/user/objects/__tests__/tfa-sign-in-result.test.js rename to api/src/user/objects/__tests__/tfa-sign-in-result.test.js index 0eaf1b70ab..e4ff9f7caa 100644 --- a/api-js/src/user/objects/__tests__/tfa-sign-in-result.test.js +++ b/api/src/user/objects/__tests__/tfa-sign-in-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { tfaSignInResult } from '../index' +import {tfaSignInResult} from '../index' describe('given the tfaSignInResult object', () => { describe('testing the field definitions', () => { @@ -24,7 +24,7 @@ describe('given the tfaSignInResult object', () => { const demoType = tfaSignInResult.getFields() expect( - demoType.authenticateToken.resolve({ authenticateToken: 'token' }), + demoType.authenticateToken.resolve({authenticateToken: 'token'}), ).toEqual('token') }) }) @@ -33,7 +33,7 @@ describe('given the tfaSignInResult object', () => { const demoType = tfaSignInResult.getFields() expect( - demoType.sendMethod.resolve({ sendMethod: 'sendMethod' }), + demoType.sendMethod.resolve({sendMethod: 'sendMethod'}), ).toEqual('sendMethod') }) }) diff --git a/api-js/src/user/objects/__tests__/update-user-password-error.test.js b/api/src/user/objects/__tests__/update-user-password-error.test.js similarity index 79% rename from api-js/src/user/objects/__tests__/update-user-password-error.test.js rename to api/src/user/objects/__tests__/update-user-password-error.test.js index 2f2937ed24..495bc5dfb0 100644 --- a/api-js/src/user/objects/__tests__/update-user-password-error.test.js +++ b/api/src/user/objects/__tests__/update-user-password-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { updateUserPasswordErrorType } from '../update-user-password-error' +import {updateUserPasswordErrorType} from '../update-user-password-error' describe('given the updateUserPasswordErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the updateUserPasswordErrorType object', () => { it('returns the resolved field', () => { const demoType = updateUserPasswordErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the updateUserPasswordErrorType object', () => { const demoType = updateUserPasswordErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api/src/user/objects/__tests__/update-user-password-result.test.js b/api/src/user/objects/__tests__/update-user-password-result.test.js new file mode 100644 index 0000000000..8722eb0717 --- /dev/null +++ b/api/src/user/objects/__tests__/update-user-password-result.test.js @@ -0,0 +1,24 @@ +import {GraphQLString} from 'graphql' + +import {updateUserPasswordResultType} from '../update-user-password-result' + +describe('given the updateUserPasswordResultType object', () => { + describe('testing the field definitions', () => { + it('has an status field', () => { + const demoType = updateUserPasswordResultType.getFields() + + expect(demoType).toHaveProperty('status') + expect(demoType.status.type).toMatchObject(GraphQLString) + }) + }) + + describe('testing the field resolvers', () => { + describe('testing the status resolver', () => { + it('returns the resolved field', () => { + const demoType = updateUserPasswordResultType.getFields() + + expect(demoType.status.resolve({status: 'status'})).toEqual('status') + }) + }) + }) +}) diff --git a/api-js/src/user/objects/__tests__/update-user-profile-error.test.js b/api/src/user/objects/__tests__/update-user-profile-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/update-user-profile-error.test.js rename to api/src/user/objects/__tests__/update-user-profile-error.test.js index a6c219860f..891899a7ac 100644 --- a/api-js/src/user/objects/__tests__/update-user-profile-error.test.js +++ b/api/src/user/objects/__tests__/update-user-profile-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { updateUserProfileErrorType } from '../index' +import {updateUserProfileErrorType} from '../index' describe('given the updateUserProfileErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the updateUserProfileErrorType object', () => { it('returns the resolved field', () => { const demoType = updateUserProfileErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the updateUserProfileErrorType object', () => { const demoType = updateUserProfileErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/update-user-profile-result.test.js b/api/src/user/objects/__tests__/update-user-profile-result.test.js similarity index 79% rename from api-js/src/user/objects/__tests__/update-user-profile-result.test.js rename to api/src/user/objects/__tests__/update-user-profile-result.test.js index aa16bd7554..201b14c2ce 100644 --- a/api-js/src/user/objects/__tests__/update-user-profile-result.test.js +++ b/api/src/user/objects/__tests__/update-user-profile-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { updateUserProfileResultType, userPersonalType } from '../index' +import {updateUserProfileResultType, userPersonalType} from '../index' describe('given the updateUserProfileResultType object', () => { describe('testing the field definitions', () => { @@ -23,14 +23,14 @@ describe('given the updateUserProfileResultType object', () => { it('returns the resolved field', () => { const demoType = updateUserProfileResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) describe('testing the user field', () => { it('returns the resolved value', () => { const demoType = updateUserProfileResultType.getFields() - expect(demoType.user.resolve({ user: { id: '1' } })).toEqual({ + expect(demoType.user.resolve({user: {id: '1'}})).toEqual({ id: '1', }) }) diff --git a/api/src/user/objects/__tests__/user-personal.test.js b/api/src/user/objects/__tests__/user-personal.test.js new file mode 100644 index 0000000000..a5aeeeb708 --- /dev/null +++ b/api/src/user/objects/__tests__/user-personal.test.js @@ -0,0 +1,292 @@ +import crypto from 'crypto' +import { GraphQLNonNull, GraphQLID, GraphQLString, GraphQLBoolean, GraphQLList } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { GraphQLEmailAddress, GraphQLPhoneNumber } from 'graphql-scalars' + +import { affiliationConnection } from '../../../affiliation/objects' +import { completedTour, dismissedMessage, userPersonalType } from '../index' +import { TfaSendMethodEnum } from '../../../enums' +import { decryptPhoneNumber } from '../../../validators' + +const { CIPHER_KEY } = process.env + +describe('given the user object', () => { + describe('testing the field definitions', () => { + it('has an id field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('id') + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) + }) + it('has a userName field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('userName') + expect(demoType.userName.type).toMatchObject(GraphQLEmailAddress) + }) + it('has a displayName field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('displayName') + expect(demoType.displayName.type).toMatchObject(GraphQLString) + }) + it('has a phoneNumber field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('phoneNumber') + expect(demoType.phoneNumber.type).toMatchObject(GraphQLPhoneNumber) + }) + it('has a phoneValidated field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('phoneValidated') + expect(demoType.phoneValidated.type).toMatchObject(GraphQLBoolean) + }) + it('has a emailValidated field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('emailValidated') + expect(demoType.emailValidated.type).toMatchObject(GraphQLBoolean) + }) + it('has a tfaSendMethod field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('tfaSendMethod') + expect(demoType.tfaSendMethod.type).toMatchObject(TfaSendMethodEnum) + }) + it('has an affiliations field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('affiliations') + expect(demoType.affiliations.type).toMatchObject(affiliationConnection.connectionType) + }) + it('has a dismissedMessages field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('dismissedMessages') + expect(demoType.dismissedMessages.type).toMatchObject(new GraphQLList(dismissedMessage)) + }) + it('has a completedTours field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType).toHaveProperty('completedTours') + expect(demoType.completedTours.type).toMatchObject(new GraphQLList(completedTour)) + }) + }) + describe('testing the field resolvers', () => { + describe('testing the id resolver', () => { + it('returns the resolved field', () => { + const demoType = userPersonalType.getFields() + + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('user', '1')) + }) + }) + describe('testing the userName field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + expect(demoType.userName.resolve({ userName: 'test@email.gc.ca' })).toEqual('test@email.gc.ca') + }) + }) + describe('testing the displayName field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + expect(demoType.displayName.resolve({ displayName: 'display name' })).toEqual('display name') + }) + }) + describe('testing the phoneNumber field', () => { + describe('testing undefined phoneDetails', () => { + it('returns null', () => { + const demoType = userPersonalType.getFields() + + const phoneDetails = undefined + + expect( + demoType.phoneNumber.resolve( + { + phoneDetails, + }, + {}, + { validators: { decryptPhoneNumber } }, + ), + ).toEqual(null) + }) + }) + describe('testing null phoneDetails', () => { + it('returns null', () => { + const demoType = userPersonalType.getFields() + + const phoneDetails = null + + expect( + demoType.phoneNumber.resolve( + { + phoneDetails, + }, + {}, + { validators: { decryptPhoneNumber } }, + ), + ).toEqual(null) + }) + }) + describe('testing defined phoneDetails', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + const phoneDetails = { + iv: crypto.randomBytes(12).toString('hex'), + phoneNumber: '12345678912', + } + + const cipher = crypto.createCipheriv('aes-256-ccm', String(CIPHER_KEY), Buffer.from(phoneDetails.iv, 'hex'), { + authTagLength: 16, + }) + let encrypted = cipher.update(phoneDetails.phoneNumber, 'utf8', 'hex') + encrypted += cipher.final('hex') + + expect( + demoType.phoneNumber.resolve( + { + phoneDetails: { + phoneNumber: encrypted, + iv: phoneDetails.iv, + tag: cipher.getAuthTag().toString('hex'), + }, + }, + {}, + { validators: { decryptPhoneNumber } }, + ), + ).toEqual(phoneDetails.phoneNumber) + }) + }) + }) + describe('testing the phoneValidated field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + expect(demoType.phoneValidated.resolve({ phoneValidated: true })).toEqual(true) + }) + }) + describe('testing the emailValidated field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + expect(demoType.emailValidated.resolve({ emailValidated: true })).toEqual(true) + }) + }) + describe('testing the tfaSendMethod field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + expect(demoType.tfaSendMethod.resolve({ tfaSendMethod: 'phone' })).toEqual('phone') + }) + }) + describe('testing the affiliations field', () => { + it('returns the resolved value', async () => { + const demoType = userPersonalType.getFields() + + const expectedResult = { + edges: [ + { + cursor: toGlobalId('affiliation', '1'), + node: { + _from: 'organizations/1', + _id: 'affiliations/1', + _key: '1', + _rev: 'rev', + _to: 'users/1', + _type: 'affiliation', + id: '1', + orgKey: '1', + permission: 'user', + userKey: '1', + }, + }, + ], + totalCount: 1, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('affiliation', '1'), + endCursor: toGlobalId('affiliation', '1'), + }, + } + + await expect( + demoType.affiliations.resolve( + { _id: '1' }, + { first: 1 }, + { + loaders: { + loadAffiliationConnectionsByUserId: jest.fn().mockReturnValue(expectedResult), + }, + }, + ), + ).resolves.toEqual(expectedResult) + }) + }) + describe('testing the dismissedMessages field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + const ts = Date.now() + + expect( + demoType.dismissedMessages.resolve({ + dismissedMessages: [ + { + messageId: 'message1', + dismissedAt: ts, + }, + { + messageId: 'message2', + dismissedAt: ts, + }, + ], + }), + ).toEqual([ + { + messageId: 'message1', + dismissedAt: ts, + }, + { + messageId: 'message2', + dismissedAt: ts, + }, + ]) + }) + }) + describe('testing the completedTours field', () => { + it('returns the resolved value', () => { + const demoType = userPersonalType.getFields() + + const ts = Date.now() + + expect( + demoType.completedTours.resolve({ + completedTours: [ + { + tourId: 'tour1', + completedAt: ts, + }, + { + tourId: 'tour2', + completedAt: ts, + }, + ], + }), + ).toEqual([ + { + tourId: 'tour1', + completedAt: ts, + }, + { + tourId: 'tour2', + completedAt: ts, + }, + ]) + }) + }) + }) +}) diff --git a/api-js/src/user/objects/__tests__/user-shared.test.js b/api/src/user/objects/__tests__/user-shared.test.js similarity index 77% rename from api-js/src/user/objects/__tests__/user-shared.test.js rename to api/src/user/objects/__tests__/user-shared.test.js index 531df6088b..4f5248a53a 100644 --- a/api-js/src/user/objects/__tests__/user-shared.test.js +++ b/api/src/user/objects/__tests__/user-shared.test.js @@ -10,7 +10,7 @@ describe('given the user object', () => { const demoType = userSharedType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a displayName field', () => { const demoType = userSharedType.getFields() @@ -31,27 +31,21 @@ describe('given the user object', () => { it('returns the resolved field', () => { const demoType = userSharedType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('user', '1'), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('user', '1')) }) }) describe('testing the displayName field', () => { it('returns the resolved value', () => { const demoType = userSharedType.getFields() - expect( - demoType.displayName.resolve({ displayName: 'Display Name' }), - ).toEqual('Display Name') + expect(demoType.displayName.resolve({ displayName: 'Display Name' })).toEqual('Display Name') }) }) describe('testing the userName field', () => { it('returns the resolved value', () => { const demoType = userSharedType.getFields() - expect( - demoType.userName.resolve({ userName: 'test@email.gc.ca' }), - ).toEqual('test@email.gc.ca') + expect(demoType.userName.resolve({ userName: 'test@email.gc.ca' })).toEqual('test@email.gc.ca') }) }) }) diff --git a/api-js/src/user/objects/__tests__/verify-account-error.test.js b/api/src/user/objects/__tests__/verify-account-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/verify-account-error.test.js rename to api/src/user/objects/__tests__/verify-account-error.test.js index c4d9bcfea6..cff33f8ae9 100644 --- a/api-js/src/user/objects/__tests__/verify-account-error.test.js +++ b/api/src/user/objects/__tests__/verify-account-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { verifyAccountErrorType } from '../index' +import {verifyAccountErrorType} from '../index' describe('given the verifyAccountErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the verifyAccountErrorType object', () => { it('returns the resolved field', () => { const demoType = verifyAccountErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the verifyAccountErrorType object', () => { const demoType = verifyAccountErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/verify-account-result.test.js b/api/src/user/objects/__tests__/verify-account-result.test.js similarity index 77% rename from api-js/src/user/objects/__tests__/verify-account-result.test.js rename to api/src/user/objects/__tests__/verify-account-result.test.js index d1996047b2..f37f10ac6c 100644 --- a/api-js/src/user/objects/__tests__/verify-account-result.test.js +++ b/api/src/user/objects/__tests__/verify-account-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { verifyAccountResultType } from '../index' +import {verifyAccountResultType} from '../index' describe('given the verifyAccountResultType object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the verifyAccountResultType object', () => { it('returns the resolved field', () => { const demoType = verifyAccountResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) }) diff --git a/api-js/src/user/objects/__tests__/verify-phone-number-error.test.js b/api/src/user/objects/__tests__/verify-phone-number-error.test.js similarity index 80% rename from api-js/src/user/objects/__tests__/verify-phone-number-error.test.js rename to api/src/user/objects/__tests__/verify-phone-number-error.test.js index 547bcc8fa6..be8fa8521d 100644 --- a/api-js/src/user/objects/__tests__/verify-phone-number-error.test.js +++ b/api/src/user/objects/__tests__/verify-phone-number-error.test.js @@ -1,6 +1,6 @@ -import { GraphQLInt, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLString} from 'graphql' -import { verifyPhoneNumberErrorType } from '../index' +import {verifyPhoneNumberErrorType} from '../index' describe('given the verifyPhoneNumberErrorType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the verifyPhoneNumberErrorType object', () => { it('returns the resolved field', () => { const demoType = verifyPhoneNumberErrorType.getFields() - expect(demoType.code.resolve({ code: 400 })).toEqual(400) + expect(demoType.code.resolve({code: 400})).toEqual(400) }) }) describe('testing the description field', () => { @@ -31,7 +31,7 @@ describe('given the verifyPhoneNumberErrorType object', () => { const demoType = verifyPhoneNumberErrorType.getFields() expect( - demoType.description.resolve({ description: 'description' }), + demoType.description.resolve({description: 'description'}), ).toEqual('description') }) }) diff --git a/api-js/src/user/objects/__tests__/verify-phone-number-result.test.js b/api/src/user/objects/__tests__/verify-phone-number-result.test.js similarity index 81% rename from api-js/src/user/objects/__tests__/verify-phone-number-result.test.js rename to api/src/user/objects/__tests__/verify-phone-number-result.test.js index 2a7fb6587b..c247e82a65 100644 --- a/api-js/src/user/objects/__tests__/verify-phone-number-result.test.js +++ b/api/src/user/objects/__tests__/verify-phone-number-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { verifyPhoneNumberResultType, userPersonalType } from '../index' +import {verifyPhoneNumberResultType, userPersonalType} from '../index' describe('given the verifyPhoneNumberResultType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the verifyPhoneNumberResultType object', () => { it('returns the resolved field', () => { const demoType = verifyPhoneNumberResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) describe('testing the user resolver', () => { @@ -34,7 +34,7 @@ describe('given the verifyPhoneNumberResultType object', () => { displayName: 'John Doe', } - expect(demoType.user.resolve({ user })).toEqual({ + expect(demoType.user.resolve({user})).toEqual({ displayName: 'John Doe', }) }) diff --git a/api/src/user/objects/auth-result.js b/api/src/user/objects/auth-result.js new file mode 100644 index 0000000000..32b1e869f3 --- /dev/null +++ b/api/src/user/objects/auth-result.js @@ -0,0 +1,19 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userPersonalType} from './user-personal' + +export const authResultType = new GraphQLObjectType({ + name: 'AuthResult', + description: `An object used to return information when users sign up or authenticate.`, + fields: () => ({ + authToken: { + type: GraphQLString, + description: `JWT used for accessing controlled content.`, + resolve: ({token}) => token, + }, + user: { + type: userPersonalType, + description: `User that has just been created or signed in.`, + resolve: ({user}) => user, + }, + }), +}) diff --git a/api-js/src/user/objects/authenticate-error.js b/api/src/user/objects/authenticate-error.js similarity index 75% rename from api-js/src/user/objects/authenticate-error.js rename to api/src/user/objects/authenticate-error.js index 344c7ee291..66e741b03c 100644 --- a/api-js/src/user/objects/authenticate-error.js +++ b/api/src/user/objects/authenticate-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const authenticateError = new GraphQLObjectType({ name: 'AuthenticateError', @@ -8,12 +8,12 @@ export const authenticateError = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/user/objects/close-account-error.js b/api/src/user/objects/close-account-error.js new file mode 100644 index 0000000000..8bf44be0fc --- /dev/null +++ b/api/src/user/objects/close-account-error.js @@ -0,0 +1,19 @@ +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' + +export const closeAccountError = new GraphQLObjectType({ + name: 'CloseAccountError', + description: + 'This object is used to inform the user if any errors occurred while closing their account.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({code}) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue encountered.', + resolve: ({description}) => description, + }, + }), +}) diff --git a/api/src/user/objects/close-account-result.js b/api/src/user/objects/close-account-result.js new file mode 100644 index 0000000000..e1bb4043d2 --- /dev/null +++ b/api/src/user/objects/close-account-result.js @@ -0,0 +1,19 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { userSharedType } from './user-shared' + +export const closeAccountResult = new GraphQLObjectType({ + name: 'CloseAccountResult', + description: 'This object is used to inform the user of the status of closing their account.', + fields: () => ({ + status: { + type: GraphQLString, + description: 'Status of closing the users account.', + resolve: ({ status }) => status, + }, + user: { + type: userSharedType, + description: 'Information of the closed user account.', + resolve: ({ user }) => user, + }, + }), +}) diff --git a/api/src/user/objects/complete-tour-error.js b/api/src/user/objects/complete-tour-error.js new file mode 100644 index 0000000000..8b71f994cf --- /dev/null +++ b/api/src/user/objects/complete-tour-error.js @@ -0,0 +1,19 @@ +import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' + +export const completeTourError = new GraphQLObjectType({ + name: 'CompleteTourError', + description: + 'This object is used to inform the user if confirming that they have completed the tour was unsuccessful.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({ code }) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue that was encountered.', + resolve: ({ description }) => description, + }, + }), +}) diff --git a/api/src/user/objects/complete-tour-result.js b/api/src/user/objects/complete-tour-result.js new file mode 100644 index 0000000000..cf045b3b4e --- /dev/null +++ b/api/src/user/objects/complete-tour-result.js @@ -0,0 +1,20 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { userPersonalType } from './user-personal' + +export const completeTourResult = new GraphQLObjectType({ + name: 'CompleteTourResult', + description: + 'This object is used to inform the user if confirming that they have completed the tour was successful or not.', + fields: () => ({ + status: { + type: GraphQLString, + description: 'Status of the message dismissal.', + resolve: ({ status }) => status, + }, + user: { + type: userPersonalType, + description: 'The user object that was updated.', + resolve: ({ user }) => user, + }, + }), +}) diff --git a/api/src/user/objects/completed-tour.js b/api/src/user/objects/completed-tour.js new file mode 100644 index 0000000000..3adc1feecf --- /dev/null +++ b/api/src/user/objects/completed-tour.js @@ -0,0 +1,19 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { GraphQLDateTime } from 'graphql-scalars' + +export const completedTour = new GraphQLObjectType({ + name: 'CompletedTour', + description: 'This object is used for returning a tour that has been completed.', + fields: () => ({ + tourId: { + type: GraphQLString, + description: 'The ID of the tour that was completed.', + resolve: ({ tourId }) => tourId, + }, + completedAt: { + type: GraphQLDateTime, + description: 'The time the tour was completed.', + resolve: ({ completedAt }) => completedAt, + }, + }), +}) diff --git a/api/src/user/objects/dismiss-message-error.js b/api/src/user/objects/dismiss-message-error.js new file mode 100644 index 0000000000..fc42a25545 --- /dev/null +++ b/api/src/user/objects/dismiss-message-error.js @@ -0,0 +1,18 @@ +import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' + +export const dismissMessageError = new GraphQLObjectType({ + name: 'DismissMessageError', + description: 'This object is used to inform the user if any errors occurred while dismissing a message.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({ code }) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue that was encountered.', + resolve: ({ description }) => description, + }, + }), +}) diff --git a/api/src/user/objects/dismiss-message-result.js b/api/src/user/objects/dismiss-message-result.js new file mode 100644 index 0000000000..a0ee09bb41 --- /dev/null +++ b/api/src/user/objects/dismiss-message-result.js @@ -0,0 +1,19 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { userPersonalType } from './user-personal' + +export const dismissMessageResult = new GraphQLObjectType({ + name: 'DismissMessageResult', + description: 'This object is used to inform the user if the message was successfully dismissed.', + fields: () => ({ + status: { + type: GraphQLString, + description: 'Status of the message dismissal.', + resolve: ({ status }) => status, + }, + user: { + type: userPersonalType, + description: 'The user object that was updated.', + resolve: ({ user }) => user, + }, + }), +}) diff --git a/api/src/user/objects/dismissed-message.js b/api/src/user/objects/dismissed-message.js new file mode 100644 index 0000000000..80b7834df4 --- /dev/null +++ b/api/src/user/objects/dismissed-message.js @@ -0,0 +1,19 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql' +import { GraphQLDateTime } from 'graphql-scalars' + +export const dismissedMessage = new GraphQLObjectType({ + name: 'DismissedMessage', + description: 'This object is used for returning a message that has been dismissed.', + fields: () => ({ + messageId: { + type: GraphQLString, + description: 'The ID of the message that was dismissed.', + resolve: ({ messageId }) => messageId, + }, + dismissedAt: { + type: GraphQLDateTime, + description: 'The time the message was dismissed.', + resolve: ({ dismissedAt }) => dismissedAt, + }, + }), +}) diff --git a/api/src/user/objects/email-update-options.js b/api/src/user/objects/email-update-options.js new file mode 100644 index 0000000000..c5467bedf8 --- /dev/null +++ b/api/src/user/objects/email-update-options.js @@ -0,0 +1,25 @@ +import { GraphQLBoolean, GraphQLObjectType } from 'graphql' + +export const emailUpdateOptionsType = new GraphQLObjectType({ + name: 'EmailUpdateOptions', + fields: () => ({ + orgFootprint: { + type: GraphQLBoolean, + description: + "Value used to determine if user wants to receive possibly daily email updates about their organization's digital footprint.", + resolve: ({ orgFootprint }) => orgFootprint, + }, + progressReport: { + type: GraphQLBoolean, + description: + "Value used to determine if user wants to receive monthly email updates about their organization's compliance score progress.", + resolve: ({ progressReport }) => progressReport, + }, + detectDecay: { + type: GraphQLBoolean, + description: + "Value used to determine if user wants to receive possibly daily email updates about their organization's compliance statuses.", + resolve: ({ detectDecay }) => detectDecay, + }, + }), +}) diff --git a/api/src/user/objects/index.js b/api/src/user/objects/index.js new file mode 100644 index 0000000000..79bb433a5f --- /dev/null +++ b/api/src/user/objects/index.js @@ -0,0 +1,32 @@ +export * from './auth-result' +export * from './authenticate-error' +export * from './close-account-error' +export * from './close-account-result' +export * from './complete-tour-error' +export * from './complete-tour-result' +export * from './completed-tour' +export * from './dismiss-message-error' +export * from './dismiss-message-result' +export * from './dismissed-message' +export * from './email-update-options' +export * from './my-tracker-result' +export * from './remove-phone-number-error' +export * from './remove-phone-number-result' +export * from './reset-password-error' +export * from './reset-password-result' +export * from './set-phone-number-error' +export * from './set-phone-number-result' +export * from './sign-in-error' +export * from './sign-up-error' +export * from './tfa-sign-in-result' +export * from './update-user-password-error' +export * from './update-user-password-result' +export * from './update-user-profile-error' +export * from './update-user-profile-result' +export * from './user-connection' +export * from './user-personal' +export * from './user-shared' +export * from './verify-phone-number-error' +export * from './verify-phone-number-result' +export * from './verify-account-error' +export * from './verify-account-result' diff --git a/api/src/user/objects/my-tracker-result.js b/api/src/user/objects/my-tracker-result.js new file mode 100644 index 0000000000..517ba42d8c --- /dev/null +++ b/api/src/user/objects/my-tracker-result.js @@ -0,0 +1,49 @@ +import { GraphQLBoolean, GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import { connectionArgs } from 'graphql-relay' + +import { organizationSummaryType } from '../../organization/objects/organization-summary' +import { domainOrder } from '../../domain/inputs' +import { domainConnection } from '../../domain/objects' + +export const myTrackerType = new GraphQLObjectType({ + name: 'MyTrackerResult', + fields: () => ({ + summaries: { + type: organizationSummaryType, + description: 'Summaries based on scan types that are preformed on the given organizations domains.', + resolve: ({ summaries }) => summaries, + }, + domainCount: { + type: GraphQLInt, + description: 'The number of domains associated with this organization.', + resolve: ({ domainCount }) => domainCount, + }, + domains: { + type: domainConnection.connectionType, + description: 'The domains which are associated with this organization.', + args: { + orderBy: { + type: domainOrder, + description: 'Ordering options for domain connections.', + }, + ownership: { + type: GraphQLBoolean, + description: 'Limit domains to those that belong to an organization that has ownership.', + }, + search: { + type: GraphQLString, + description: 'String used to search for domains.', + }, + ...connectionArgs, + }, + resolve: async ({ _id }, args, { loaders: { loadDomainConnectionsByUserId } }) => { + const connections = await loadDomainConnectionsByUserId({ + ...args, + myTracker: true, + }) + return connections + }, + }, + }), + description: 'Organization object containing information for a given Organization.', +}) diff --git a/api-js/src/user/objects/remove-phone-number-error.js b/api/src/user/objects/remove-phone-number-error.js similarity index 76% rename from api-js/src/user/objects/remove-phone-number-error.js rename to api/src/user/objects/remove-phone-number-error.js index b0a8011659..ffff03a8de 100644 --- a/api-js/src/user/objects/remove-phone-number-error.js +++ b/api/src/user/objects/remove-phone-number-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const removePhoneNumberErrorType = new GraphQLObjectType({ name: 'RemovePhoneNumberError', @@ -8,12 +8,12 @@ export const removePhoneNumberErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api-js/src/user/objects/remove-phone-number-result.js b/api/src/user/objects/remove-phone-number-result.js similarity index 80% rename from api-js/src/user/objects/remove-phone-number-result.js rename to api/src/user/objects/remove-phone-number-result.js index 7045e1ffec..9ffb7c5537 100644 --- a/api-js/src/user/objects/remove-phone-number-result.js +++ b/api/src/user/objects/remove-phone-number-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const removePhoneNumberResultType = new GraphQLObjectType({ name: 'RemovePhoneNumberResult', @@ -9,7 +9,7 @@ export const removePhoneNumberResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if the phone number removal was successful.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), }) diff --git a/api-js/src/user/objects/reset-password-error.js b/api/src/user/objects/reset-password-error.js similarity index 75% rename from api-js/src/user/objects/reset-password-error.js rename to api/src/user/objects/reset-password-error.js index d0353e04ba..cbcba93ee5 100644 --- a/api-js/src/user/objects/reset-password-error.js +++ b/api/src/user/objects/reset-password-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const resetPasswordErrorType = new GraphQLObjectType({ name: 'ResetPasswordError', @@ -8,12 +8,12 @@ export const resetPasswordErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api-js/src/user/objects/reset-password-result.js b/api/src/user/objects/reset-password-result.js similarity index 88% rename from api-js/src/user/objects/reset-password-result.js rename to api/src/user/objects/reset-password-result.js index 658ee9f284..13e340ac24 100644 --- a/api-js/src/user/objects/reset-password-result.js +++ b/api/src/user/objects/reset-password-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const resetPasswordResultType = new GraphQLObjectType({ name: 'ResetPasswordResult', diff --git a/api-js/src/user/objects/set-phone-number-error.js b/api/src/user/objects/set-phone-number-error.js similarity index 75% rename from api-js/src/user/objects/set-phone-number-error.js rename to api/src/user/objects/set-phone-number-error.js index 0374905d0e..017ac5ef67 100644 --- a/api-js/src/user/objects/set-phone-number-error.js +++ b/api/src/user/objects/set-phone-number-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const setPhoneNumberErrorType = new GraphQLObjectType({ name: 'SetPhoneNumberError', @@ -8,12 +8,12 @@ export const setPhoneNumberErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/user/objects/set-phone-number-result.js b/api/src/user/objects/set-phone-number-result.js new file mode 100644 index 0000000000..3b8b6795dd --- /dev/null +++ b/api/src/user/objects/set-phone-number-result.js @@ -0,0 +1,21 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userPersonalType} from './user-personal' + +export const setPhoneNumberResultType = new GraphQLObjectType({ + name: 'SetPhoneNumberResult', + description: + 'This object is used to inform the user that no errors were encountered while setting a new phone number.', + fields: () => ({ + status: { + type: GraphQLString, + description: + 'Informs the user if their phone code was successfully sent.', + resolve: ({status}) => status, + }, + user: { + type: userPersonalType, + description: 'The user who set their phone number.', + resolve: ({user}) => user, + }, + }), +}) diff --git a/api/src/user/objects/sign-in-error.js b/api/src/user/objects/sign-in-error.js new file mode 100644 index 0000000000..a3037d43bf --- /dev/null +++ b/api/src/user/objects/sign-in-error.js @@ -0,0 +1,19 @@ +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' + +export const signInError = new GraphQLObjectType({ + name: 'SignInError', + description: + 'This object is used to inform the user if any errors occurred during sign in.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({code}) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue that was encountered.', + resolve: ({description}) => description, + }, + }), +}) diff --git a/api/src/user/objects/sign-up-error.js b/api/src/user/objects/sign-up-error.js new file mode 100644 index 0000000000..eeb87064e5 --- /dev/null +++ b/api/src/user/objects/sign-up-error.js @@ -0,0 +1,19 @@ +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' + +export const signUpError = new GraphQLObjectType({ + name: 'SignUpError', + description: + 'This object is used to inform the user if any errors occurred during sign up.', + fields: () => ({ + code: { + type: GraphQLInt, + description: 'Error code to inform user what the issue is related to.', + resolve: ({code}) => code, + }, + description: { + type: GraphQLString, + description: 'Description of the issue that was encountered.', + resolve: ({description}) => description, + }, + }), +}) diff --git a/api/src/user/objects/tfa-sign-in-result.js b/api/src/user/objects/tfa-sign-in-result.js new file mode 100644 index 0000000000..08bd0053d5 --- /dev/null +++ b/api/src/user/objects/tfa-sign-in-result.js @@ -0,0 +1,20 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' + +export const tfaSignInResult = new GraphQLObjectType({ + name: 'TFASignInResult', + description: + 'This object is used when the user signs in and has validated either their email or phone.', + fields: () => ({ + authenticateToken: { + type: GraphQLString, + description: 'Token used to verify during authentication.', + resolve: ({authenticateToken}) => authenticateToken, + }, + sendMethod: { + type: GraphQLString, + description: + 'Whether the authentication code was sent through text, or email.', + resolve: ({sendMethod}) => sendMethod, + }, + }), +}) diff --git a/api-js/src/user/objects/update-user-password-error.js b/api/src/user/objects/update-user-password-error.js similarity index 75% rename from api-js/src/user/objects/update-user-password-error.js rename to api/src/user/objects/update-user-password-error.js index 7708ba6106..c52e6cf611 100644 --- a/api-js/src/user/objects/update-user-password-error.js +++ b/api/src/user/objects/update-user-password-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const updateUserPasswordErrorType = new GraphQLObjectType({ name: 'UpdateUserPasswordError', @@ -8,12 +8,12 @@ export const updateUserPasswordErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api-js/src/user/objects/update-user-password-result.js b/api/src/user/objects/update-user-password-result.js similarity index 80% rename from api-js/src/user/objects/update-user-password-result.js rename to api/src/user/objects/update-user-password-result.js index 4ab81653f2..ed471795e9 100644 --- a/api-js/src/user/objects/update-user-password-result.js +++ b/api/src/user/objects/update-user-password-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const updateUserPasswordResultType = new GraphQLObjectType({ name: 'UpdateUserPasswordResultType', @@ -9,7 +9,7 @@ export const updateUserPasswordResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if their password was successfully updated.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), }) diff --git a/api-js/src/user/objects/update-user-profile-error.js b/api/src/user/objects/update-user-profile-error.js similarity index 75% rename from api-js/src/user/objects/update-user-profile-error.js rename to api/src/user/objects/update-user-profile-error.js index 10123abda2..93e56f9af2 100644 --- a/api-js/src/user/objects/update-user-profile-error.js +++ b/api/src/user/objects/update-user-profile-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const updateUserProfileErrorType = new GraphQLObjectType({ name: 'UpdateUserProfileError', @@ -8,12 +8,12 @@ export const updateUserProfileErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/user/objects/update-user-profile-result.js b/api/src/user/objects/update-user-profile-result.js new file mode 100644 index 0000000000..cdb01ee1e0 --- /dev/null +++ b/api/src/user/objects/update-user-profile-result.js @@ -0,0 +1,21 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userPersonalType} from './user-personal' + +export const updateUserProfileResultType = new GraphQLObjectType({ + name: 'UpdateUserProfileResult', + description: + 'This object is used to inform the user that no errors were encountered while resetting their password.', + fields: () => ({ + status: { + type: GraphQLString, + description: + 'Informs the user if the password reset was successful, and to redirect to sign in page.', + resolve: ({status}) => status, + }, + user: { + type: userPersonalType, + description: 'Return the newly updated user information.', + resolve: ({user}) => user, + }, + }), +}) diff --git a/api/src/user/objects/user-connection.js b/api/src/user/objects/user-connection.js new file mode 100644 index 0000000000..194e6ec0f3 --- /dev/null +++ b/api/src/user/objects/user-connection.js @@ -0,0 +1,16 @@ +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' + +import {userSharedType} from './user-shared' + +export const userConnection = connectionDefinitions({ + name: 'User', + nodeType: userSharedType, + connectionFields: () => ({ + totalCount: { + type: GraphQLInt, + description: 'The total amount of users the user has access to.', + resolve: ({totalCount}) => totalCount, + }, + }), +}) diff --git a/api/src/user/objects/user-personal.js b/api/src/user/objects/user-personal.js new file mode 100644 index 0000000000..453160df06 --- /dev/null +++ b/api/src/user/objects/user-personal.js @@ -0,0 +1,99 @@ +import { GraphQLBoolean, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { connectionArgs, globalIdField } from 'graphql-relay' +import { GraphQLEmailAddress, GraphQLPhoneNumber } from 'graphql-scalars' + +import { affiliationOrgOrder } from '../../affiliation/inputs' +import { affiliationConnection } from '../../affiliation/objects' +import { TfaSendMethodEnum } from '../../enums' +import { nodeInterface } from '../../node' +import { emailUpdateOptionsType } from './email-update-options' +import { dismissedMessage } from './dismissed-message' +import { completedTour } from './completed-tour' + +export const userPersonalType = new GraphQLObjectType({ + name: 'PersonalUser', + fields: () => ({ + id: globalIdField('user'), + userName: { + type: GraphQLEmailAddress, + description: 'Users email address.', + resolve: ({ userName }) => userName, + }, + displayName: { + type: GraphQLString, + description: 'Name displayed to other users.', + resolve: ({ displayName }) => displayName, + }, + phoneNumber: { + type: GraphQLPhoneNumber, + description: 'The phone number the user has setup with tfa.', + resolve: ({ phoneDetails }, _args, { validators: { decryptPhoneNumber } }) => { + if (typeof phoneDetails === 'undefined' || phoneDetails === null) { + return null + } + return decryptPhoneNumber(phoneDetails) + }, + }, + phoneValidated: { + type: GraphQLBoolean, + description: 'Has the user completed phone validation.', + resolve: ({ phoneValidated }) => phoneValidated, + }, + emailValidated: { + type: GraphQLBoolean, + description: 'Has the user email verified their account.', + resolve: ({ emailValidated }) => emailValidated, + }, + tfaSendMethod: { + type: TfaSendMethodEnum, + description: 'The method in which TFA codes are sent.', + resolve: ({ tfaSendMethod }) => tfaSendMethod, + }, + insideUser: { + type: GraphQLBoolean, + description: 'Does the user want to see new features in progress.', + resolve: ({ insideUser }) => insideUser, + }, + emailUpdateOptions: { + type: emailUpdateOptionsType, + description: + 'A number of different emails the user can optionally receive periodically that provide updates about their organization.', + resolve: ({ emailUpdateOptions }) => emailUpdateOptions, + }, + affiliations: { + type: affiliationConnection.connectionType, + description: 'Users affiliations to various organizations.', + args: { + orderBy: { + type: affiliationOrgOrder, + description: 'Ordering options for affiliation connections.', + }, + search: { + type: GraphQLString, + description: 'String used to search for affiliated organizations.', + }, + ...connectionArgs, + }, + resolve: async ({ _id }, args, { loaders: { loadAffiliationConnectionsByUserId } }) => { + const affiliations = await loadAffiliationConnectionsByUserId({ + userId: _id, + ...args, + }) + return affiliations + }, + }, + dismissedMessages: { + type: new GraphQLList(dismissedMessage), + description: 'Messages that the user has dismissed.', + resolve: ({ dismissedMessages }) => dismissedMessages || [], + }, + completedTours: { + type: new GraphQLList(completedTour), + description: 'Tours the user has completed.', + resolve: ({ completedTours }) => completedTours || [], + }, + }), + interfaces: [nodeInterface], + description: `This object is used for showing personal user details, +and is used for only showing the details of the querying user.`, +}) diff --git a/api/src/user/objects/user-shared.js b/api/src/user/objects/user-shared.js new file mode 100644 index 0000000000..188c3c0e5c --- /dev/null +++ b/api/src/user/objects/user-shared.js @@ -0,0 +1,63 @@ +import {GraphQLBoolean, GraphQLObjectType, GraphQLString} from 'graphql' +import {connectionArgs, globalIdField} from 'graphql-relay' +import {GraphQLEmailAddress} from 'graphql-scalars' +import {affiliationOrgOrder} from '../../affiliation/inputs' +import {affiliationConnection} from '../../affiliation/objects' + +import {nodeInterface} from '../../node' + +export const userSharedType = new GraphQLObjectType({ + name: 'SharedUser', + fields: () => ({ + id: globalIdField('user'), + displayName: { + type: GraphQLString, + description: 'Users display name.', + resolve: ({displayName}) => displayName, + }, + userName: { + type: GraphQLEmailAddress, + description: 'Users email address.', + resolve: ({userName}) => userName, + }, + emailValidated: { + type: GraphQLBoolean, + description: 'Has the user email verified their account.', + resolve: ({emailValidated}) => emailValidated, + }, + insideUser: { + type: GraphQLBoolean, + description: 'Does the user want to see new features in progress.', + resolve: ({ insideUser }) => insideUser, + }, + affiliations: { + type: affiliationConnection.connectionType, + description: 'Users affiliations to various organizations.', + args: { + orderBy: { + type: affiliationOrgOrder, + description: 'Ordering options for affiliation connections.', + }, + search: { + type: GraphQLString, + description: 'String used to search for affiliated organizations.', + }, + ...connectionArgs, + }, + resolve: async ( + {_id}, + args, + {loaders: {loadAffiliationConnectionsByUserId}}, + ) => { + const affiliations = await loadAffiliationConnectionsByUserId({ + userId: _id, + ...args, + }) + return affiliations + }, + }, + }), + interfaces: [nodeInterface], + description: `This object is used for showing none personal user details, +and is used for limiting admins to the personal details of users.`, +}) diff --git a/api-js/src/user/objects/verify-account-error.js b/api/src/user/objects/verify-account-error.js similarity index 75% rename from api-js/src/user/objects/verify-account-error.js rename to api/src/user/objects/verify-account-error.js index a059781f43..9f829797b0 100644 --- a/api-js/src/user/objects/verify-account-error.js +++ b/api/src/user/objects/verify-account-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const verifyAccountErrorType = new GraphQLObjectType({ name: 'VerifyAccountError', @@ -8,12 +8,12 @@ export const verifyAccountErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api-js/src/user/objects/verify-account-result.js b/api/src/user/objects/verify-account-result.js similarity index 79% rename from api-js/src/user/objects/verify-account-result.js rename to api/src/user/objects/verify-account-result.js index 4b5560e12c..7bc423b81f 100644 --- a/api-js/src/user/objects/verify-account-result.js +++ b/api/src/user/objects/verify-account-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const verifyAccountResultType = new GraphQLObjectType({ name: 'VerifyAccountResult', @@ -9,7 +9,7 @@ export const verifyAccountResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if their account was successfully verified.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), }) diff --git a/api-js/src/user/objects/verify-phone-number-error.js b/api/src/user/objects/verify-phone-number-error.js similarity index 76% rename from api-js/src/user/objects/verify-phone-number-error.js rename to api/src/user/objects/verify-phone-number-error.js index 37adde6437..a21a1b4047 100644 --- a/api-js/src/user/objects/verify-phone-number-error.js +++ b/api/src/user/objects/verify-phone-number-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const verifyPhoneNumberErrorType = new GraphQLObjectType({ name: 'VerifyPhoneNumberError', @@ -8,12 +8,12 @@ export const verifyPhoneNumberErrorType = new GraphQLObjectType({ code: { type: GraphQLInt, description: 'Error code to inform user what the issue is related to.', - resolve: ({ code }) => code, + resolve: ({code}) => code, }, description: { type: GraphQLString, description: 'Description of the issue that was encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/user/objects/verify-phone-number-result.js b/api/src/user/objects/verify-phone-number-result.js new file mode 100644 index 0000000000..b9f3e42f87 --- /dev/null +++ b/api/src/user/objects/verify-phone-number-result.js @@ -0,0 +1,22 @@ +import {GraphQLObjectType, GraphQLString} from 'graphql' + +import {userPersonalType} from './user-personal' + +export const verifyPhoneNumberResultType = new GraphQLObjectType({ + name: 'VerifyPhoneNumberResult', + description: + 'This object is used to inform the user that no errors were encountered while verifying their phone number.', + fields: () => ({ + status: { + type: GraphQLString, + description: + 'Informs the user if their phone number was successfully verified.', + resolve: ({status}) => status, + }, + user: { + type: userPersonalType, + description: 'The user who verified their phone number.', + resolve: ({user}) => user, + }, + }), +}) diff --git a/api-js/src/user/queries/__tests__/find-me.test.js b/api/src/user/queries/__tests__/find-me.test.js similarity index 76% rename from api-js/src/user/queries/__tests__/find-me.test.js rename to api/src/user/queries/__tests__/find-me.test.js index 3eda1f14bd..68d8a5ad98 100644 --- a/api-js/src/user/queries/__tests__/find-me.test.js +++ b/api/src/user/queries/__tests__/find-me.test.js @@ -1,14 +1,15 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema } from 'graphql' import { toGlobalId } from 'graphql-relay' -import { databaseOptions } from '../../../../database-options' import { userRequired } from '../../../auth' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { loadAffiliationConnectionsByUserId } from '../../../affiliation/loaders' import { loadUserByKey } from '../../loaders' import { cleanseInput } from '../../../validators' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,11 +24,15 @@ describe('given the findMe query', () => { }) // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) @@ -44,7 +49,6 @@ describe('given the findMe query', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -61,17 +65,17 @@ describe('given the findMe query', () => { describe('users successfully performs query', () => { it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findMe { id } } `, - null, - { + rootValue: null, + contextValue: { auth: { userRequired: userRequired({ userKey: user._key, @@ -79,15 +83,14 @@ describe('given the findMe query', () => { }), }, loaders: { - loadAffiliationConnectionsByUserId: - loadAffiliationConnectionsByUserId({ - query, - userKey: user._key, - cleanseInput, - }), + loadAffiliationConnectionsByUserId: loadAffiliationConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + }), }, }, - ) + }) const expectedResponse = { data: { diff --git a/api/src/user/queries/__tests__/find-my-tracker.test.js b/api/src/user/queries/__tests__/find-my-tracker.test.js new file mode 100644 index 0000000000..b635fcb630 --- /dev/null +++ b/api/src/user/queries/__tests__/find-my-tracker.test.js @@ -0,0 +1,410 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey, loadMyTrackerByUserId } from '../../loaders' +import { loadDomainConnectionsByUserId } from '../../../domain/loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given findMyTracker query', () => { + let query, drop, truncate, schema, collections, orgOne, orgTwo, i18n, user + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + beforeEach(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + consoleOutput.length = 0 + }) + describe('given a successful load', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + + orgOne = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + orgTwo = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'not-treasury-board-secretariat', + acronym: 'NTBS', + name: 'Not Treasury Board of Canada Secretariat', + zone: 'NFED', + sector: 'NTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'ne-pas-secretariat-conseil-tresor', + acronym: 'NPSCT', + name: 'Ne Pas Secrétariat du Conseil Trésor du Canada', + zone: 'NPFED', + sector: 'NPTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: orgOne._id, + _to: user._id, + permission: 'user', + }) + await collections.affiliations.save({ + _from: orgTwo._id, + _to: user._id, + permission: 'user', + }) + }) + describe('given successful retrieval of domains', () => { + describe('user queries for myTracker', () => { + describe('in english', () => { + it('returns myTracker results', async () => { + const response = await graphql({ + schema, + source: ` + query { + findMyTracker { + summaries { + https { + categories { + name + count + percentage + } + total + } + dmarcPhase { + categories { + name + count + percentage + } + total + } + } + domainCount + domains(first: 10) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + node { + id + domain + hasDMARCReport + status { + ciphers + curves + dkim + dmarc + hsts + https + policy + protocols + spf + ssl + } + } + cursor + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + }, + loaders: { + loadMyTrackerByUserId: loadMyTrackerByUserId({ + query, + userKey: user._key, + cleanseInput, + language: 'en', + }), + loadDomainConnectionsByUserId: loadDomainConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + language: 'en', + auth: { loginRequiredBool: true }, + }), + }, + }, + }) + + const expectedResponse = { + data: { + findMyTracker: { + summaries: { + https: { + categories: [ + { + name: 'pass', + count: 0, + percentage: 0, + }, + { + name: 'fail', + count: 0, + percentage: 0, + }, + ], + total: 0, + }, + dmarcPhase: { + categories: [ + { + name: 'assess', + count: 0, + percentage: 0, + }, + { + name: 'deploy', + count: 0, + percentage: 0, + }, + { + name: 'enforce', + count: 0, + percentage: 0, + }, + { + name: 'maintain', + count: 0, + percentage: 0, + }, + ], + total: 0, + }, + }, + domainCount: 0, + domains: { + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + totalCount: 0, + edges: [], + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved personal domains.`]) + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful load', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('database error occurs', () => { + it('returns an error message', async () => { + const mockedQuery = jest.fn().mockRejectedValueOnce(new Error('Database error occurred.')) + + const response = await graphql({ + schema, + source: ` + query { + findMyTracker { + summaries { + https { + categories { + name + count + percentage + } + total + } + dmarcPhase { + categories { + name + count + percentage + } + total + } + } + domainCount + domains(first: 10) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + node { + id + domain + hasDMARCReport + status { + ciphers + curves + dkim + dmarc + hsts + https + policy + protocols + spf + ssl + } + } + cursor + } + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: user._key, + auth: { + checkSuperAdmin: jest.fn(), + userRequired: jest.fn().mockReturnValue({}), + verifiedRequired: jest.fn(), + }, + loaders: { + loadMyTrackerByUserId: loadMyTrackerByUserId({ + query: mockedQuery, + userKey: user._key, + cleanseInput, + language: 'en', + i18n, + }), + }, + }, + }) + + const error = [new GraphQLError('Unable to query domain(s). Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Database error occurred while user: ${user._key} was trying to query domains in loadDomainsByUser, error: Error: Database error occurred.`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/user/queries/__tests__/find-my-users.test.js b/api/src/user/queries/__tests__/find-my-users.test.js new file mode 100644 index 0000000000..f4a7bed846 --- /dev/null +++ b/api/src/user/queries/__tests__/find-my-users.test.js @@ -0,0 +1,375 @@ +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkSuperAdmin, superAdminRequired, userRequired, verifiedRequired } from '../../../auth' +import { loadUserByKey, loadUserConnectionsByUserId } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given findMyUsersQuery', () => { + let query, drop, truncate, schema, collections, saOrg, orgOne, orgTwo, i18n, superAdmin, user1, user2 + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + }) + beforeEach(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + consoleOutput.length = 0 + }) + describe('given a successful load', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + superAdmin = await collections.users.save({ + displayName: 'Super Admin', + userName: 'super.admin@istio.actually.exists', + emailValidated: true, + }) + + user1 = await collections.users.save({ + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + + user2 = await collections.users.save({ + displayName: 'Real User', + userName: 'real.user@istio.actually.exists', + emailValidated: true, + }) + + saOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'FED', + sector: 'SA', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'FED', + sector: 'SA', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + orgOne = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + orgTwo = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'not-treasury-board-secretariat', + acronym: 'NTBS', + name: 'Not Treasury Board of Canada Secretariat', + zone: 'NFED', + sector: 'NTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'ne-pas-secretariat-conseil-tresor', + acronym: 'NPSCT', + name: 'Ne Pas Secrétariat du Conseil Trésor du Canada', + zone: 'NPFED', + sector: 'NPTBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: saOrg._id, + _to: superAdmin._id, + permission: 'super_admin', + }) + await collections.affiliations.save({ + _from: orgOne._id, + _to: user1._id, + permission: 'user', + }) + await collections.affiliations.save({ + _from: orgTwo._id, + _to: user1._id, + permission: 'user', + }) + await collections.affiliations.save({ + _from: orgTwo._id, + _to: user2._id, + permission: 'admin', + }) + }) + describe('given successful retrieval of domains', () => { + describe('super admin queries for their users', () => { + describe('in english', () => { + it('returns users with affiliations', async () => { + const response = await graphql({ + schema, + source: ` + query { + findMyUsers(first: 10) { + edges { + cursor + node { + id + userName + displayName + emailValidated + } + } + pageInfo { + hasNextPage + endCursor + hasPreviousPage + startCursor + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: superAdmin._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: superAdmin._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: superAdmin._key, + loadUserByKey: loadUserByKey({ + query, + userKey: superAdmin._key, + i18n, + }), + }), + verifiedRequired: verifiedRequired({}), + superAdminRequired: superAdminRequired({}), + }, + loaders: { + loadUserConnectionsByUserId: loadUserConnectionsByUserId({ + query, + userKey: superAdmin._key, + cleanseInput, + auth: { loginRequired: true }, + language: 'en', + }), + }, + }, + }) + + const expectedResponse = { + data: { + findMyUsers: { + edges: [ + { + cursor: toGlobalId('user', superAdmin._key), + node: { + id: toGlobalId('user', superAdmin._key), + displayName: 'Super Admin', + userName: 'super.admin@istio.actually.exists', + emailValidated: true, + }, + }, + { + cursor: toGlobalId('user', user1._key), + node: { + id: toGlobalId('user', user1._key), + displayName: 'Test Account', + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }, + }, + { + cursor: toGlobalId('user', user2._key), + node: { + id: toGlobalId('user', user2._key), + displayName: 'Real User', + userName: 'real.user@istio.actually.exists', + emailValidated: true, + }, + }, + ], + pageInfo: { + endCursor: toGlobalId('user', user2._key), + hasNextPage: false, + hasPreviousPage: false, + startCursor: toGlobalId('user', superAdmin._key), + }, + }, + }, + } + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${superAdmin._key} successfully retrieved their users.`]) + }) + }) + }) + }) + }) + + describe('given an unsuccessful load', () => { + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('database error occurs', () => { + it('returns an error message', async () => { + const mockedQuery = jest.fn().mockRejectedValueOnce(new Error('Database error occurred.')) + + const response = await graphql({ + schema, + source: ` + query { + findMyUsers(first: 10) { + edges { + cursor + node { + id + userName + displayName + emailValidated + } + } + pageInfo { + hasNextPage + endCursor + hasPreviousPage + startCursor + } + } + } + `, + rootValue: null, + contextValue: { + i18n, + userKey: superAdmin._key, + auth: { + checkSuperAdmin: jest.fn(), + userRequired: jest.fn().mockReturnValue({}), + verifiedRequired: jest.fn(), + superAdminRequired: jest.fn(), + }, + loaders: { + loadUserConnectionsByUserId: loadUserConnectionsByUserId({ + query: mockedQuery, + userKey: superAdmin._key, + cleanseInput, + auth: { loginRequired: true }, + language: 'en', + i18n, + }), + }, + }, + }) + + const error = [new GraphQLError('Unable to query user(s). Please try again.')] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Database error occurred while user: ${superAdmin._key} was trying to query users in loadUserConnectionsByUserId, error: Error: Database error occurred.`, + ]) + }) + }) + }) + }) + }) +}) diff --git a/api-js/src/user/queries/__tests__/find-user-by-username.test.js b/api/src/user/queries/__tests__/find-user-by-username.test.js similarity index 88% rename from api-js/src/user/queries/__tests__/find-user-by-username.test.js rename to api/src/user/queries/__tests__/find-user-by-username.test.js index f0e4bfd636..6006d15417 100644 --- a/api-js/src/user/queries/__tests__/find-user-by-username.test.js +++ b/api/src/user/queries/__tests__/find-user-by-username.test.js @@ -1,9 +1,9 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' -import { databaseOptions } from '../../../../database-options' import { userRequired, checkUserIsAdminForUser } from '../../../auth' import { createQuerySchema } from '../../../query' import { cleanseInput } from '../../../validators' @@ -11,6 +11,7 @@ import { createMutationSchema } from '../../../mutation' import { loadUserByKey, loadUserByUserName } from '../../loaders' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -40,25 +41,27 @@ describe('given the findUserByUsername query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) userTwo = await collections.users.save({ userName: 'test.accounttwo@istio.actually.exists', displayName: 'Test Account Two', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -120,9 +123,9 @@ describe('given the findUserByUsername query', () => { ` }) it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -132,8 +135,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -154,7 +157,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -181,9 +184,9 @@ describe('given the findUserByUsername query', () => { }) }) it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -193,8 +196,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -215,7 +218,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -257,9 +260,9 @@ describe('given the findUserByUsername query', () => { ` }) it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -269,8 +272,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -291,7 +294,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -318,9 +321,9 @@ describe('given the findUserByUsername query', () => { }) }) it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -330,8 +333,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -352,7 +355,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -386,9 +389,9 @@ describe('given the findUserByUsername query', () => { }) describe('if the user is an admin for a different organization', () => { it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -398,8 +401,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -418,17 +421,15 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) - expect(response.errors).toEqual([ - new GraphQLError('User could not be queried.'), - ]) + }) + expect(response.errors).toEqual([new GraphQLError('User could not be queried.')]) }) }) describe('if the user is only a user for their organization(s)', () => { it('will return error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -438,8 +439,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -458,14 +459,12 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const error = [new GraphQLError(`User could not be queried.`)] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User 123 is not permitted to query users.`, - ]) + expect(consoleOutput).toEqual([`User 123 is not permitted to query users.`]) }) }) }) @@ -486,9 +485,9 @@ describe('given the findUserByUsername query', () => { }) describe('if the user is an admin for a different organization', () => { it('will return specified user', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -498,8 +497,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -518,17 +517,15 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) - expect(response.errors).toEqual([ - new GraphQLError(`L'utilisateur n'a pas pu être interrogé.`), - ]) + }) + expect(response.errors).toEqual([new GraphQLError(`L'utilisateur n'a pas pu être interrogé.`)]) }) }) describe('if the user is only a user for their organization(s)', () => { it('will return error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findUserByUsername( userName: "test.accounttwo@istio.actually.exists" @@ -538,8 +535,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -558,16 +555,12 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError(`L'utilisateur n'a pas pu être interrogé.`), - ] + const error = [new GraphQLError(`L'utilisateur n'a pas pu être interrogé.`)] expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User 123 is not permitted to query users.`, - ]) + expect(consoleOutput).toEqual([`User 123 is not permitted to query users.`]) }) }) }) diff --git a/api-js/src/user/queries/__tests__/is-user-admin.test.js b/api/src/user/queries/__tests__/is-user-admin.test.js similarity index 87% rename from api-js/src/user/queries/__tests__/is-user-admin.test.js rename to api/src/user/queries/__tests__/is-user-admin.test.js index eddb129a55..3328d3ec5e 100644 --- a/api-js/src/user/queries/__tests__/is-user-admin.test.js +++ b/api/src/user/queries/__tests__/is-user-admin.test.js @@ -1,9 +1,9 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLError, GraphQLSchema } from 'graphql' import { toGlobalId } from 'graphql-relay' -import { databaseOptions } from '../../../../database-options' import { checkPermission, userRequired } from '../../../auth' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' @@ -12,6 +12,7 @@ import { cleanseInput } from '../../../validators' import { loadOrgByKey } from '../../../organization/loaders' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -40,18 +41,21 @@ describe('given the isUserAdmin query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -105,15 +109,15 @@ describe('given the isUserAdmin query', () => { ` }) it('will return true', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -131,7 +135,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -152,15 +156,15 @@ describe('given the isUserAdmin query', () => { ` }) it('will return true', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -178,7 +182,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -199,15 +203,15 @@ describe('given the isUserAdmin query', () => { ` }) it('will return false', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -225,7 +229,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -248,15 +252,15 @@ describe('given the isUserAdmin query', () => { ` }) it('will return true', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin (orgId: "${toGlobalId('organizations', org._key)}") } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -274,7 +278,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -295,15 +299,15 @@ describe('given the isUserAdmin query', () => { ` }) it('will return true', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin (orgId: "${toGlobalId('organizations', org._key)}") } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -321,7 +325,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -342,15 +346,15 @@ describe('given the isUserAdmin query', () => { ` }) it('will return false', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin (orgId: "${toGlobalId('organizations', org._key)}") } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -368,7 +372,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -398,20 +402,18 @@ describe('given the isUserAdmin query', () => { }) describe('database error occurs', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -428,13 +430,9 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) - const error = [ - new GraphQLError( - `Unable to verify if user is an admin, please try again.`, - ), - ] - + }) + const error = [new GraphQLError(`Unable to verify if user is an admin, please try again.`)] + expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 was seeing if they were an admin, err: Error: Database error occurred.`, @@ -458,20 +456,18 @@ describe('given the isUserAdmin query', () => { }) describe('database error occurs', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserAdmin } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -488,14 +484,12 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) - + }) + const error = [ - new GraphQLError( - `Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.`, - ), + new GraphQLError(`Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.`), ] - + expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 was seeing if they were an admin, err: Error: Database error occurred.`, diff --git a/api-js/src/user/queries/__tests__/is-user-super-admin.test.js b/api/src/user/queries/__tests__/is-user-super-admin.test.js similarity index 86% rename from api-js/src/user/queries/__tests__/is-user-super-admin.test.js rename to api/src/user/queries/__tests__/is-user-super-admin.test.js index 18fa101ca3..48dfa1cf39 100644 --- a/api-js/src/user/queries/__tests__/is-user-super-admin.test.js +++ b/api/src/user/queries/__tests__/is-user-super-admin.test.js @@ -1,14 +1,15 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLError, GraphQLSchema } from 'graphql' -import { databaseOptions } from '../../../../database-options' import { checkPermission, userRequired } from '../../../auth' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { loadUserByKey } from '../../loaders' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -37,18 +38,21 @@ describe('given the isUserSuperAdmin query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -100,15 +104,15 @@ describe('given the isUserSuperAdmin query', () => { ` }) it('will return true', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserSuperAdmin } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -122,7 +126,7 @@ describe('given the isUserSuperAdmin query', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -143,15 +147,15 @@ describe('given the isUserSuperAdmin query', () => { ` }) it('will return false', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserSuperAdmin } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -165,7 +169,7 @@ describe('given the isUserSuperAdmin query', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -186,15 +190,15 @@ describe('given the isUserSuperAdmin query', () => { ` }) it('will return false', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserSuperAdmin } `, - null, - { + rootValue: null, + contextValue: { userKey: user._key, query: query, auth: { @@ -208,7 +212,7 @@ describe('given the isUserSuperAdmin query', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -237,20 +241,18 @@ describe('given the isUserSuperAdmin query', () => { }) describe('database error occurs', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserSuperAdmin } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -259,13 +261,9 @@ describe('given the isUserSuperAdmin query', () => { }), }, }, - ) + }) - const error = [ - new GraphQLError( - `Unable to verify if user is a super admin, please try again.`, - ), - ] + const error = [new GraphQLError(`Unable to verify if user is a super admin, please try again.`)] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 was seeing if they were a super admin, err: Error: Database error occurred.`, @@ -290,20 +288,18 @@ describe('given the isUserSuperAdmin query', () => { }) describe('database error occurs', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { isUserSuperAdmin } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -312,7 +308,7 @@ describe('given the isUserSuperAdmin query', () => { }), }, }, - ) + }) const error = [ new GraphQLError( diff --git a/api/src/user/queries/find-me.js b/api/src/user/queries/find-me.js new file mode 100644 index 0000000000..f5426dff48 --- /dev/null +++ b/api/src/user/queries/find-me.js @@ -0,0 +1,14 @@ +import {userPersonalType} from '../objects' + +export const findMe = { + type: userPersonalType, + description: 'Query the currently logged in user.', + resolve: async (_, __, {auth: {userRequired}}) => { + // Get querying user + const user = await userRequired() + + user.id = user._key + + return user + }, +} diff --git a/api/src/user/queries/find-my-tracker.js b/api/src/user/queries/find-my-tracker.js new file mode 100644 index 0000000000..0f73c9cb57 --- /dev/null +++ b/api/src/user/queries/find-my-tracker.js @@ -0,0 +1,36 @@ +import { t } from '@lingui/macro' + +const { myTrackerType } = require('../objects') + +export const findMyTracker = { + type: myTrackerType, + description: + 'Select all information on a selected organization that a user has access to.', + resolve: async ( + _, + __, + { + i18n, + userKey, + auth: { userRequired, verifiedRequired }, + loaders: { loadMyTrackerByUserId }, + }, + ) => { + // Get User + const user = await userRequired() + verifiedRequired({ user }) + + // Retrieve organization by slug + const myTracker = await loadMyTrackerByUserId() + + if (typeof myTracker === 'undefined') { + console.warn(`User ${userKey} could not retrieve organization.`) + throw new Error( + i18n._(t`No organization with the provided slug could be found.`), + ) + } + + console.info(`User ${userKey} successfully retrieved personal domains.`) + return myTracker + }, +} diff --git a/api/src/user/queries/find-my-users.js b/api/src/user/queries/find-my-users.js new file mode 100644 index 0000000000..38ae198971 --- /dev/null +++ b/api/src/user/queries/find-my-users.js @@ -0,0 +1,45 @@ +import { GraphQLString } from 'graphql' +import { connectionArgs } from 'graphql-relay' + +import { userOrder } from '../../user/inputs' +import { userConnection } from '../objects/user-connection' + +export const findMyUsers = { + type: userConnection.connectionType, + description: 'Select users an admin has access to.', + args: { + orderBy: { + type: userOrder, + description: 'Ordering options for user affiliation', + }, + search: { + type: GraphQLString, + description: 'String used to search for users.', + }, + ...connectionArgs, + }, + resolve: async ( + _, + args, + { + userKey, + auth: { checkSuperAdmin, userRequired, verifiedRequired, superAdminRequired }, + loaders: { loadUserConnectionsByUserId }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const isSuperAdmin = await checkSuperAdmin() + superAdminRequired({ user, isSuperAdmin }) + + const userConnections = await loadUserConnectionsByUserId({ + isSuperAdmin, + ...args, + }) + + console.info(`User: ${userKey} successfully retrieved their users.`) + + return userConnections + }, +} diff --git a/api-js/src/user/queries/find-user-by-username.js b/api/src/user/queries/find-user-by-username.js similarity index 95% rename from api-js/src/user/queries/find-user-by-username.js rename to api/src/user/queries/find-user-by-username.js index ae1aa5d799..dcd8c3c986 100644 --- a/api-js/src/user/queries/find-user-by-username.js +++ b/api/src/user/queries/find-user-by-username.js @@ -8,7 +8,7 @@ export const findUserByUsername = { type: userSharedType, args: { userName: { - type: GraphQLNonNull(GraphQLEmailAddress), + type: new GraphQLNonNull(GraphQLEmailAddress), description: 'Email address of user you wish to gather data for.', }, }, diff --git a/api/src/user/queries/index.js b/api/src/user/queries/index.js new file mode 100644 index 0000000000..6fb8a36b56 --- /dev/null +++ b/api/src/user/queries/index.js @@ -0,0 +1,7 @@ +export * from './find-me' +export * from './find-my-tracker' +export * from './find-my-users' +export * from './find-user-by-username' +export * from './login-required' +export * from './is-user-admin' +export * from './is-user-super-admin' diff --git a/api/src/user/queries/is-user-admin.js b/api/src/user/queries/is-user-admin.js new file mode 100644 index 0000000000..108947b7a2 --- /dev/null +++ b/api/src/user/queries/is-user-admin.js @@ -0,0 +1,54 @@ +import { t } from '@lingui/macro' +import { GraphQLBoolean, GraphQLID } from 'graphql' +import { fromGlobalId } from 'graphql-relay' + +export const isUserAdmin = { + type: GraphQLBoolean, + description: 'Query used to check if the user has an admin role.', + args: { + orgId: { + type: GraphQLID, + description: 'Optional org id to see if user is an admin for the requested org.', + }, + }, + resolve: async ( + _, + args, + { + i18n, + query, + userKey, + auth: { checkPermission, userRequired }, + loaders: { loadOrgByKey }, + validators: { cleanseInput }, + }, + ) => { + const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) + const user = await userRequired() + + // check if for a specific org + if (orgKey) { + const org = await loadOrgByKey.load(orgKey) + const permission = await checkPermission({ orgId: org._id }) + + return ['admin', 'owner', 'super_admin'].includes(permission) + } + + // check to see if user is an admin or higher for at least one org + let userAdmin + try { + userAdmin = await query` + WITH users, affiliations + FOR v, e IN 1..1 INBOUND ${user._id} affiliations + FILTER e.permission IN ["admin", "owner", "super_admin"] + LIMIT 1 + RETURN e.permission + ` + } catch (err) { + console.error(`Database error occurred when user: ${userKey} was seeing if they were an admin, err: ${err}`) + throw new Error(i18n._(t`Unable to verify if user is an admin, please try again.`)) + } + + return userAdmin.count > 0 + }, +} diff --git a/api/src/user/queries/is-user-super-admin.js b/api/src/user/queries/is-user-super-admin.js new file mode 100644 index 0000000000..f634f08ac9 --- /dev/null +++ b/api/src/user/queries/is-user-super-admin.js @@ -0,0 +1,34 @@ +import {GraphQLBoolean} from 'graphql' +import {t} from '@lingui/macro' + +export const isUserSuperAdmin = { + type: GraphQLBoolean, + description: 'Query used to check if the user has a super admin role.', + resolve: async (_, __, {i18n, query, userKey, auth: {userRequired}}) => { + const user = await userRequired() + + let userAdmin + try { + userAdmin = await query` + WITH users, affiliations + FOR v, e IN 1..1 INBOUND ${user._id} affiliations + FILTER e.permission == "super_admin" + LIMIT 1 + RETURN e.permission + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} was seeing if they were a super admin, err: ${err}`, + ) + throw new Error( + i18n._(t`Unable to verify if user is a super admin, please try again.`), + ) + } + + if (userAdmin.count > 0) { + return true + } + + return false + }, +} diff --git a/api/src/user/queries/login-required.js b/api/src/user/queries/login-required.js new file mode 100644 index 0000000000..6d4013cabe --- /dev/null +++ b/api/src/user/queries/login-required.js @@ -0,0 +1,9 @@ +import {GraphQLBoolean} from 'graphql' + +export const loginRequired = { + type: GraphQLBoolean, + description: 'Checks if user must be logged in to access data.', + resolve: async (_, __, {auth: {loginRequiredBool}}) => { + return loginRequiredBool + }, +} diff --git a/api-js/src/user/unions/__tests__/authenticate-union.test.js b/api/src/user/unions/__tests__/authenticate-union.test.js similarity index 84% rename from api-js/src/user/unions/__tests__/authenticate-union.test.js rename to api/src/user/unions/__tests__/authenticate-union.test.js index 6fffa1721a..8b035a7e1a 100644 --- a/api-js/src/user/unions/__tests__/authenticate-union.test.js +++ b/api/src/user/unions/__tests__/authenticate-union.test.js @@ -22,7 +22,7 @@ describe('given the authenticateUnion', () => { authResult: {}, } - expect(authenticateUnion.resolveType(obj)).toMatchObject(authResultType) + expect(authenticateUnion.resolveType(obj)).toMatch(authResultType.name) }) }) describe('testing the authenticateError type', () => { @@ -33,9 +33,7 @@ describe('given the authenticateUnion', () => { description: 'text', } - expect(authenticateUnion.resolveType(obj)).toMatchObject( - authenticateError, - ) + expect(authenticateUnion.resolveType(obj)).toMatch(authenticateError.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/close-account-union.test.js b/api/src/user/unions/__tests__/close-account-union.test.js similarity index 83% rename from api-js/src/user/unions/__tests__/close-account-union.test.js rename to api/src/user/unions/__tests__/close-account-union.test.js index 9544ab2a3f..20d97773ca 100644 --- a/api-js/src/user/unions/__tests__/close-account-union.test.js +++ b/api/src/user/unions/__tests__/close-account-union.test.js @@ -22,9 +22,7 @@ describe('given the closeAccountUnion', () => { authResult: {}, } - expect(closeAccountUnion.resolveType(obj)).toMatchObject( - closeAccountResult, - ) + expect(closeAccountUnion.resolveType(obj)).toMatch(closeAccountResult.name) }) }) describe('testing the closeAccountError', () => { @@ -36,9 +34,7 @@ describe('given the closeAccountUnion', () => { description: 'text', } - expect(closeAccountUnion.resolveType(obj)).toMatchObject( - closeAccountError, - ) + expect(closeAccountUnion.resolveType(obj)).toMatch(closeAccountError.name) }) }) }) diff --git a/api/src/user/unions/__tests__/complete-tour-union.test.js b/api/src/user/unions/__tests__/complete-tour-union.test.js new file mode 100644 index 0000000000..7dd3880040 --- /dev/null +++ b/api/src/user/unions/__tests__/complete-tour-union.test.js @@ -0,0 +1,41 @@ +import { completeTourResult, completeTourError } from '../../objects/index' +import { completeTourUnion } from '../complete-tour-union' + +describe('given the completeTourUnion', () => { + describe('testing the field types', () => { + it('contains completeTourResult', () => { + const demoType = completeTourUnion.getTypes() + + expect(demoType).toContain(completeTourResult) + }) + it('contains completeTourError', () => { + const demoType = completeTourUnion.getTypes() + + expect(demoType).toContain(completeTourError) + }) + }) + describe('testing the field selection', () => { + describe('testing the completeTourResult', () => { + it('returns the correct type', () => { + const obj = { + _type: 'success', + status: '', + user: {}, + } + + expect(completeTourUnion.resolveType(obj)).toMatch(completeTourResult.name) + }) + }) + describe('testing the completeTourError', () => { + it('returns the correct type', () => { + const obj = { + _type: 'error', + code: 400, + description: '', + } + + expect(completeTourUnion.resolveType(obj)).toMatch(completeTourError.name) + }) + }) + }) +}) diff --git a/api/src/user/unions/__tests__/dismiss-message-union.test.js b/api/src/user/unions/__tests__/dismiss-message-union.test.js new file mode 100644 index 0000000000..e4c81abdb9 --- /dev/null +++ b/api/src/user/unions/__tests__/dismiss-message-union.test.js @@ -0,0 +1,41 @@ +import { dismissMessageResult, dismissMessageError } from '../../objects/index' +import { dismissMessageUnion } from '../dismiss-message-union' + +describe('given the dismissMessageUnion', () => { + describe('testing the field types', () => { + it('contains dismissMessageResult', () => { + const demoType = dismissMessageUnion.getTypes() + + expect(demoType).toContain(dismissMessageResult) + }) + it('contains dismissMessageError', () => { + const demoType = dismissMessageUnion.getTypes() + + expect(demoType).toContain(dismissMessageError) + }) + }) + describe('testing the field selection', () => { + describe('testing the dismissMessageResult', () => { + it('returns the correct type', () => { + const obj = { + _type: 'success', + status: '', + user: {}, + } + + expect(dismissMessageUnion.resolveType(obj)).toMatch(dismissMessageResult.name) + }) + }) + describe('testing the dismissMessageError', () => { + it('returns the correct type', () => { + const obj = { + _type: 'error', + code: 400, + description: '', + } + + expect(dismissMessageUnion.resolveType(obj)).toMatch(dismissMessageError.name) + }) + }) + }) +}) diff --git a/api-js/src/user/unions/__tests__/refresh-tokens-union.test.js b/api/src/user/unions/__tests__/refresh-tokens-union.test.js similarity index 83% rename from api-js/src/user/unions/__tests__/refresh-tokens-union.test.js rename to api/src/user/unions/__tests__/refresh-tokens-union.test.js index dd75e4238a..803f987ff5 100644 --- a/api-js/src/user/unions/__tests__/refresh-tokens-union.test.js +++ b/api/src/user/unions/__tests__/refresh-tokens-union.test.js @@ -22,9 +22,7 @@ describe('given the refreshTokensUnion', () => { authResult: {}, } - expect(refreshTokensUnion.resolveType(obj)).toMatchObject( - authResultType, - ) + expect(refreshTokensUnion.resolveType(obj)).toMatch(authResultType.name) }) }) describe('testing the authenticateError type', () => { @@ -35,9 +33,7 @@ describe('given the refreshTokensUnion', () => { description: 'text', } - expect(refreshTokensUnion.resolveType(obj)).toMatchObject( - authenticateError, - ) + expect(refreshTokensUnion.resolveType(obj)).toMatch(authenticateError.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/remove-phone-number-union.test.js b/api/src/user/unions/__tests__/remove-phone-number-union.test.js similarity index 76% rename from api-js/src/user/unions/__tests__/remove-phone-number-union.test.js rename to api/src/user/unions/__tests__/remove-phone-number-union.test.js index cb1f7d3a84..01e1a577f5 100644 --- a/api-js/src/user/unions/__tests__/remove-phone-number-union.test.js +++ b/api/src/user/unions/__tests__/remove-phone-number-union.test.js @@ -1,7 +1,4 @@ -import { - removePhoneNumberErrorType, - removePhoneNumberResultType, -} from '../../objects/index' +import { removePhoneNumberErrorType, removePhoneNumberResultType } from '../../objects/index' import { removePhoneNumberUnion } from '../remove-phone-number-union' describe('given the removePhoneNumberUnion', () => { @@ -25,9 +22,7 @@ describe('given the removePhoneNumberUnion', () => { authResult: {}, } - expect(removePhoneNumberUnion.resolveType(obj)).toMatchObject( - removePhoneNumberResultType, - ) + expect(removePhoneNumberUnion.resolveType(obj)).toMatch(removePhoneNumberResultType.name) }) }) describe('testing the removePhoneNumberErrorType', () => { @@ -39,9 +34,7 @@ describe('given the removePhoneNumberUnion', () => { description: 'text', } - expect(removePhoneNumberUnion.resolveType(obj)).toMatchObject( - removePhoneNumberErrorType, - ) + expect(removePhoneNumberUnion.resolveType(obj)).toMatch(removePhoneNumberErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/reset-password-union.test.js b/api/src/user/unions/__tests__/reset-password-union.test.js similarity index 76% rename from api-js/src/user/unions/__tests__/reset-password-union.test.js rename to api/src/user/unions/__tests__/reset-password-union.test.js index fc6b2ebf4a..0e3132cb81 100644 --- a/api-js/src/user/unions/__tests__/reset-password-union.test.js +++ b/api/src/user/unions/__tests__/reset-password-union.test.js @@ -1,7 +1,4 @@ -import { - resetPasswordErrorType, - resetPasswordResultType, -} from '../../objects/index' +import { resetPasswordErrorType, resetPasswordResultType } from '../../objects/index' import { resetPasswordUnion } from '../reset-password-union' describe('given the resetPasswordUnion', () => { @@ -25,9 +22,7 @@ describe('given the resetPasswordUnion', () => { authResult: {}, } - expect(resetPasswordUnion.resolveType(obj)).toMatchObject( - resetPasswordResultType, - ) + expect(resetPasswordUnion.resolveType(obj)).toMatch(resetPasswordResultType.name) }) }) describe('testing the resetPasswordErrorType', () => { @@ -39,9 +34,7 @@ describe('given the resetPasswordUnion', () => { description: 'text', } - expect(resetPasswordUnion.resolveType(obj)).toMatchObject( - resetPasswordErrorType, - ) + expect(resetPasswordUnion.resolveType(obj)).toMatch(resetPasswordErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/set-phone-number-union.test.js b/api/src/user/unions/__tests__/set-phone-number-union.test.js similarity index 76% rename from api-js/src/user/unions/__tests__/set-phone-number-union.test.js rename to api/src/user/unions/__tests__/set-phone-number-union.test.js index 730d83b32d..05eca64b68 100644 --- a/api-js/src/user/unions/__tests__/set-phone-number-union.test.js +++ b/api/src/user/unions/__tests__/set-phone-number-union.test.js @@ -1,7 +1,4 @@ -import { - setPhoneNumberErrorType, - setPhoneNumberResultType, -} from '../../objects/index' +import { setPhoneNumberErrorType, setPhoneNumberResultType } from '../../objects/index' import { setPhoneNumberUnion } from '../index' describe('given the setPhoneNumberUnion', () => { @@ -25,9 +22,7 @@ describe('given the setPhoneNumberUnion', () => { authResult: {}, } - expect(setPhoneNumberUnion.resolveType(obj)).toMatchObject( - setPhoneNumberResultType, - ) + expect(setPhoneNumberUnion.resolveType(obj)).toMatch(setPhoneNumberResultType.name) }) }) describe('testing the setPhoneNumberErrorType', () => { @@ -39,9 +34,7 @@ describe('given the setPhoneNumberUnion', () => { description: 'text', } - expect(setPhoneNumberUnion.resolveType(obj)).toMatchObject( - setPhoneNumberErrorType, - ) + expect(setPhoneNumberUnion.resolveType(obj)).toMatch(setPhoneNumberErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/sign-in-union.test.js b/api/src/user/unions/__tests__/sign-in-union.test.js similarity index 81% rename from api-js/src/user/unions/__tests__/sign-in-union.test.js rename to api/src/user/unions/__tests__/sign-in-union.test.js index 689c0d9e68..a54349d56a 100644 --- a/api-js/src/user/unions/__tests__/sign-in-union.test.js +++ b/api/src/user/unions/__tests__/sign-in-union.test.js @@ -1,8 +1,4 @@ -import { - authResultType, - signInError, - tfaSignInResult, -} from '../../objects/index' +import { authResultType, signInError, tfaSignInResult } from '../../objects/index' import { signInUnion } from '../sign-in-union' describe('given the sign in union', () => { @@ -31,7 +27,7 @@ describe('given the sign in union', () => { authResult: {}, } - expect(signInUnion.resolveType(obj)).toMatchObject(authResultType) + expect(signInUnion.resolveType(obj)).toMatch(authResultType.name) }) }) describe('testing the signInError type', () => { @@ -43,7 +39,7 @@ describe('given the sign in union', () => { description: 'text', } - expect(signInUnion.resolveType(obj)).toMatchObject(signInError) + expect(signInUnion.resolveType(obj)).toMatch(signInError.name) }) }) describe('testing the tfaSignInResult type', () => { @@ -54,7 +50,7 @@ describe('given the sign in union', () => { authenticateToken: 'token', } - expect(signInUnion.resolveType(obj)).toMatchObject(tfaSignInResult) + expect(signInUnion.resolveType(obj)).toMatch(tfaSignInResult.name) }) }) }) diff --git a/api/src/user/unions/__tests__/sign-up-union.test.js b/api/src/user/unions/__tests__/sign-up-union.test.js new file mode 100644 index 0000000000..d513109381 --- /dev/null +++ b/api/src/user/unions/__tests__/sign-up-union.test.js @@ -0,0 +1,41 @@ +import { tfaSignInResult, signUpError } from '../../objects/index' +import { signUpUnion } from '../sign-up-union' + +describe('given the sign up union', () => { + describe('testing the field types', () => { + it('contains tfaSignInResult type', () => { + const demoType = signUpUnion.getTypes() + + expect(demoType).toContain(tfaSignInResult) + }) + it('contains signUpError type', () => { + const demoType = signUpUnion.getTypes() + + expect(demoType).toContain(signUpError) + }) + }) + describe('testing the field selection', () => { + describe('testing the authResult type', () => { + it('returns the correct type', () => { + const obj = { + _type: 'tfa', + authResult: {}, + } + + expect(signUpUnion.resolveType(obj)).toMatch(tfaSignInResult.name) + }) + }) + describe('testing the signUpError type', () => { + it('returns the correct type', () => { + const obj = { + _type: 'error', + error: 'sign-in-error', + code: 401, + description: 'text', + } + + expect(signUpUnion.resolveType(obj)).toMatch(signUpError.name) + }) + }) + }) +}) diff --git a/api-js/src/user/unions/__tests__/update-user-password-union.test.js b/api/src/user/unions/__tests__/update-user-password-union.test.js similarity index 84% rename from api-js/src/user/unions/__tests__/update-user-password-union.test.js rename to api/src/user/unions/__tests__/update-user-password-union.test.js index 850aa2a905..96d88f2ee8 100644 --- a/api-js/src/user/unions/__tests__/update-user-password-union.test.js +++ b/api/src/user/unions/__tests__/update-user-password-union.test.js @@ -1,7 +1,4 @@ -import { - updateUserPasswordErrorType, - updateUserPasswordResultType, -} from '../../objects/index' +import { updateUserPasswordErrorType, updateUserPasswordResultType } from '../../objects/index' import { updateUserPasswordUnion } from '../update-user-password-union' describe('given the updateUserPasswordUnion', () => { @@ -25,9 +22,7 @@ describe('given the updateUserPasswordUnion', () => { authResult: {}, } - expect(updateUserPasswordUnion.resolveType(obj)).toMatchObject( - updateUserPasswordResultType, - ) + expect(updateUserPasswordUnion.resolveType(obj)).toMatch(updateUserPasswordResultType.name) }) }) describe('testing the updateUserPasswordErrorType', () => { @@ -39,9 +34,7 @@ describe('given the updateUserPasswordUnion', () => { description: 'text', } - expect(updateUserPasswordUnion.resolveType(obj)).toMatchObject( - updateUserPasswordErrorType, - ) + expect(updateUserPasswordUnion.resolveType(obj)).toMatch(updateUserPasswordErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/update-user-profile-union.test.js b/api/src/user/unions/__tests__/update-user-profile-union.test.js similarity index 75% rename from api-js/src/user/unions/__tests__/update-user-profile-union.test.js rename to api/src/user/unions/__tests__/update-user-profile-union.test.js index fe302fa264..152da5a5f7 100644 --- a/api-js/src/user/unions/__tests__/update-user-profile-union.test.js +++ b/api/src/user/unions/__tests__/update-user-profile-union.test.js @@ -1,7 +1,4 @@ -import { - updateUserProfileResultType, - updateUserProfileErrorType, -} from '../../objects/index' +import { updateUserProfileResultType, updateUserProfileErrorType } from '../../objects/index' import { updateUserProfileUnion } from '../update-user-profile-union' describe('given the updateUserProfileUnion', () => { @@ -25,9 +22,7 @@ describe('given the updateUserProfileUnion', () => { authResult: {}, } - expect(updateUserProfileUnion.resolveType(obj)).toMatchObject( - updateUserProfileResultType, - ) + expect(updateUserProfileUnion.resolveType(obj)).toMatch(updateUserProfileResultType.name) }) }) describe('testing the updateUserProfileErrorType', () => { @@ -38,9 +33,7 @@ describe('given the updateUserProfileUnion', () => { description: 'text', } - expect(updateUserProfileUnion.resolveType(obj)).toMatchObject( - updateUserProfileErrorType, - ) + expect(updateUserProfileUnion.resolveType(obj)).toMatch(updateUserProfileErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/verify-account-union.test.js b/api/src/user/unions/__tests__/verify-account-union.test.js similarity index 76% rename from api-js/src/user/unions/__tests__/verify-account-union.test.js rename to api/src/user/unions/__tests__/verify-account-union.test.js index e100d222a5..5b4b5e3830 100644 --- a/api-js/src/user/unions/__tests__/verify-account-union.test.js +++ b/api/src/user/unions/__tests__/verify-account-union.test.js @@ -1,7 +1,4 @@ -import { - verifyAccountErrorType, - verifyAccountResultType, -} from '../../objects/index' +import { verifyAccountErrorType, verifyAccountResultType } from '../../objects/index' import { verifyAccountUnion } from '../verify-account-union' describe('given the verifyAccountUnion', () => { @@ -25,9 +22,7 @@ describe('given the verifyAccountUnion', () => { authResult: {}, } - expect(verifyAccountUnion.resolveType(obj)).toMatchObject( - verifyAccountResultType, - ) + expect(verifyAccountUnion.resolveType(obj)).toMatch(verifyAccountResultType.name) }) }) describe('testing the verifyAccountErrorType', () => { @@ -39,9 +34,7 @@ describe('given the verifyAccountUnion', () => { description: 'text', } - expect(verifyAccountUnion.resolveType(obj)).toMatchObject( - verifyAccountErrorType, - ) + expect(verifyAccountUnion.resolveType(obj)).toMatch(verifyAccountErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/__tests__/verify-phone-number-union.test.js b/api/src/user/unions/__tests__/verify-phone-number-union.test.js similarity index 75% rename from api-js/src/user/unions/__tests__/verify-phone-number-union.test.js rename to api/src/user/unions/__tests__/verify-phone-number-union.test.js index 6abd158ee8..e86daea476 100644 --- a/api-js/src/user/unions/__tests__/verify-phone-number-union.test.js +++ b/api/src/user/unions/__tests__/verify-phone-number-union.test.js @@ -1,7 +1,4 @@ -import { - verifyPhoneNumberErrorType, - verifyPhoneNumberResultType, -} from '../../objects/index' +import { verifyPhoneNumberErrorType, verifyPhoneNumberResultType } from '../../objects/index' import { verifyPhoneNumberUnion } from '../index' describe('given the verifyPhoneNumberUnion', () => { @@ -25,9 +22,7 @@ describe('given the verifyPhoneNumberUnion', () => { authResult: {}, } - expect(verifyPhoneNumberUnion.resolveType(obj)).toMatchObject( - verifyPhoneNumberResultType, - ) + expect(verifyPhoneNumberUnion.resolveType(obj)).toMatch(verifyPhoneNumberResultType.name) }) }) describe('testing the verifyPhoneNumberErrorType', () => { @@ -38,9 +33,7 @@ describe('given the verifyPhoneNumberUnion', () => { description: 'text', } - expect(verifyPhoneNumberUnion.resolveType(obj)).toMatchObject( - verifyPhoneNumberErrorType, - ) + expect(verifyPhoneNumberUnion.resolveType(obj)).toMatch(verifyPhoneNumberErrorType.name) }) }) }) diff --git a/api-js/src/user/unions/authenticate-union.js b/api/src/user/unions/authenticate-union.js similarity index 87% rename from api-js/src/user/unions/authenticate-union.js rename to api/src/user/unions/authenticate-union.js index 0d42f86ad0..8d9e56a822 100644 --- a/api-js/src/user/unions/authenticate-union.js +++ b/api/src/user/unions/authenticate-union.js @@ -8,9 +8,9 @@ export const authenticateUnion = new GraphQLUnionType({ types: [authResultType, authenticateError], resolveType({ _type }) { if (_type === 'authResult') { - return authResultType + return authResultType.name } else { - return authenticateError + return authenticateError.name } }, }) diff --git a/api/src/user/unions/close-account-union.js b/api/src/user/unions/close-account-union.js new file mode 100644 index 0000000000..be84f80337 --- /dev/null +++ b/api/src/user/unions/close-account-union.js @@ -0,0 +1,15 @@ +import { GraphQLUnionType } from 'graphql' +import { closeAccountError, closeAccountResult } from '../objects' + +export const closeAccountUnion = new GraphQLUnionType({ + name: 'CloseAccountUnion', + description: 'This union is used for the `closeAccount` mutation, to support successful or errors that may occur.', + types: [closeAccountResult, closeAccountError], + resolveType({ _type }) { + if (_type === 'error') { + return closeAccountError.name + } else { + return closeAccountResult.name + } + }, +}) diff --git a/api/src/user/unions/complete-tour-union.js b/api/src/user/unions/complete-tour-union.js new file mode 100644 index 0000000000..24b1400882 --- /dev/null +++ b/api/src/user/unions/complete-tour-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { completeTourError, completeTourResult } from '../objects' + +export const completeTourUnion = new GraphQLUnionType({ + name: 'CompleteTourUnion', + description: + 'This union is used to inform the user if confirming that they have completed the tour was successful or not.', + types: [completeTourResult, completeTourError], + resolveType({ _type }) { + if (_type === 'success') { + return completeTourResult.name + } else { + return completeTourError.name + } + }, +}) diff --git a/api/src/user/unions/dismiss-message-union.js b/api/src/user/unions/dismiss-message-union.js new file mode 100644 index 0000000000..6f7be0531a --- /dev/null +++ b/api/src/user/unions/dismiss-message-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { dismissMessageError, dismissMessageResult } from '../objects' + +export const dismissMessageUnion = new GraphQLUnionType({ + name: 'DismissMessageUnion', + description: + 'This union is used to inform the user if the message was successfully dismissed or if any errors occurred while dismissing a message.', + types: [dismissMessageError, dismissMessageResult], + resolveType({ _type }) { + if (_type === 'success') { + return dismissMessageResult.name + } else { + return dismissMessageError.name + } + }, +}) diff --git a/api/src/user/unions/index.js b/api/src/user/unions/index.js new file mode 100644 index 0000000000..a116a965b4 --- /dev/null +++ b/api/src/user/unions/index.js @@ -0,0 +1,14 @@ +export * from './authenticate-union' +export * from './close-account-union' +export * from './complete-tour-union' +export * from './dismiss-message-union' +export * from './refresh-tokens-union' +export * from './remove-phone-number-union' +export * from './reset-password-union' +export * from './set-phone-number-union' +export * from './sign-in-union' +export * from './sign-up-union' +export * from './update-user-password-union' +export * from './update-user-profile-union' +export * from './verify-phone-number-union' +export * from './verify-account-union' diff --git a/api-js/src/user/unions/refresh-tokens-union.js b/api/src/user/unions/refresh-tokens-union.js similarity index 87% rename from api-js/src/user/unions/refresh-tokens-union.js rename to api/src/user/unions/refresh-tokens-union.js index 17d3439157..9287e20d25 100644 --- a/api-js/src/user/unions/refresh-tokens-union.js +++ b/api/src/user/unions/refresh-tokens-union.js @@ -8,9 +8,9 @@ export const refreshTokensUnion = new GraphQLUnionType({ types: [authResultType, authenticateError], resolveType({ _type }) { if (_type === 'authResult') { - return authResultType + return authResultType.name } else { - return authenticateError + return authenticateError.name } }, }) diff --git a/api/src/user/unions/remove-phone-number-union.js b/api/src/user/unions/remove-phone-number-union.js new file mode 100644 index 0000000000..36b9f995dd --- /dev/null +++ b/api/src/user/unions/remove-phone-number-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { removePhoneNumberErrorType, removePhoneNumberResultType } from '../objects' + +export const removePhoneNumberUnion = new GraphQLUnionType({ + name: 'RemovePhoneNumberUnion', + description: + 'This union is used with the `RemovePhoneNumber` mutation, allowing for users to remove their phone number, and support any errors that may occur', + types: [removePhoneNumberErrorType, removePhoneNumberResultType], + resolveType({ _type }) { + if (_type === 'result') { + return removePhoneNumberResultType.name + } else { + return removePhoneNumberErrorType.name + } + }, +}) diff --git a/api-js/src/user/unions/reset-password-union.js b/api/src/user/unions/reset-password-union.js similarity index 86% rename from api-js/src/user/unions/reset-password-union.js rename to api/src/user/unions/reset-password-union.js index ef85cb1e3f..b1795fe5eb 100644 --- a/api-js/src/user/unions/reset-password-union.js +++ b/api/src/user/unions/reset-password-union.js @@ -8,9 +8,9 @@ export const resetPasswordUnion = new GraphQLUnionType({ types: [resetPasswordErrorType, resetPasswordResultType], resolveType({ _type }) { if (_type === 'regular') { - return resetPasswordResultType + return resetPasswordResultType.name } else { - return resetPasswordErrorType + return resetPasswordErrorType.name } }, }) diff --git a/api-js/src/user/unions/set-phone-number-union.js b/api/src/user/unions/set-phone-number-union.js similarity index 86% rename from api-js/src/user/unions/set-phone-number-union.js rename to api/src/user/unions/set-phone-number-union.js index 1b1313f6e3..d38fe063e7 100644 --- a/api-js/src/user/unions/set-phone-number-union.js +++ b/api/src/user/unions/set-phone-number-union.js @@ -8,9 +8,9 @@ export const setPhoneNumberUnion = new GraphQLUnionType({ types: [setPhoneNumberErrorType, setPhoneNumberResultType], resolveType({ _type }) { if (_type === 'regular') { - return setPhoneNumberResultType + return setPhoneNumberResultType.name } else { - return setPhoneNumberErrorType + return setPhoneNumberErrorType.name } }, }) diff --git a/api-js/src/user/unions/sign-in-union.js b/api/src/user/unions/sign-in-union.js similarity index 84% rename from api-js/src/user/unions/sign-in-union.js rename to api/src/user/unions/sign-in-union.js index 7dacaf1807..50de60f116 100644 --- a/api-js/src/user/unions/sign-in-union.js +++ b/api/src/user/unions/sign-in-union.js @@ -8,11 +8,11 @@ export const signInUnion = new GraphQLUnionType({ types: [authResultType, signInError, tfaSignInResult], resolveType({ _type }) { if (_type === 'tfa') { - return tfaSignInResult + return tfaSignInResult.name } else if (_type === 'regular') { - return authResultType + return authResultType.name } else { - return signInError + return signInError.name } }, }) diff --git a/api/src/user/unions/sign-up-union.js b/api/src/user/unions/sign-up-union.js new file mode 100644 index 0000000000..90f738b8d6 --- /dev/null +++ b/api/src/user/unions/sign-up-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { signUpError, tfaSignInResult } from '../objects' + +export const signUpUnion = new GraphQLUnionType({ + name: 'SignUpUnion', + description: + 'This union is used with the `signUp` mutation, allowing for the user to sign up, and support any errors that may occur.', + types: [tfaSignInResult, signUpError], + resolveType({ _type }) { + if (_type === 'tfa') { + return tfaSignInResult.name + } else { + return signUpError.name + } + }, +}) diff --git a/api/src/user/unions/update-user-password-union.js b/api/src/user/unions/update-user-password-union.js new file mode 100644 index 0000000000..15e32a3adf --- /dev/null +++ b/api/src/user/unions/update-user-password-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { updateUserPasswordErrorType, updateUserPasswordResultType } from '../objects' + +export const updateUserPasswordUnion = new GraphQLUnionType({ + name: 'UpdateUserPasswordUnion', + description: + 'This union is used with the `updateUserPassword` mutation, allowing for users to update their password, and support any errors that may occur', + types: [updateUserPasswordErrorType, updateUserPasswordResultType], + resolveType({ _type }) { + if (_type === 'regular') { + return updateUserPasswordResultType.name + } else { + return updateUserPasswordErrorType.name + } + }, +}) diff --git a/api/src/user/unions/update-user-profile-union.js b/api/src/user/unions/update-user-profile-union.js new file mode 100644 index 0000000000..00287b56e3 --- /dev/null +++ b/api/src/user/unions/update-user-profile-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { updateUserProfileErrorType, updateUserProfileResultType } from '../objects' + +export const updateUserProfileUnion = new GraphQLUnionType({ + name: 'UpdateUserProfileUnion', + description: + 'This union is used with the `updateUserProfile` mutation, allowing for users to update their profile, and support any errors that may occur', + types: [updateUserProfileErrorType, updateUserProfileResultType], + resolveType({ _type }) { + if (_type === 'success') { + return updateUserProfileResultType.name + } else { + return updateUserProfileErrorType.name + } + }, +}) diff --git a/api-js/src/user/unions/verify-account-union.js b/api/src/user/unions/verify-account-union.js similarity index 86% rename from api-js/src/user/unions/verify-account-union.js rename to api/src/user/unions/verify-account-union.js index b509bcefe1..b44f12c7a3 100644 --- a/api-js/src/user/unions/verify-account-union.js +++ b/api/src/user/unions/verify-account-union.js @@ -8,9 +8,9 @@ export const verifyAccountUnion = new GraphQLUnionType({ types: [verifyAccountErrorType, verifyAccountResultType], resolveType({ _type }) { if (_type === 'success') { - return verifyAccountResultType + return verifyAccountResultType.name } else { - return verifyAccountErrorType + return verifyAccountErrorType.name } }, }) diff --git a/api/src/user/unions/verify-phone-number-union.js b/api/src/user/unions/verify-phone-number-union.js new file mode 100644 index 0000000000..61fb2def55 --- /dev/null +++ b/api/src/user/unions/verify-phone-number-union.js @@ -0,0 +1,16 @@ +import { GraphQLUnionType } from 'graphql' +import { verifyPhoneNumberErrorType, verifyPhoneNumberResultType } from '../objects' + +export const verifyPhoneNumberUnion = new GraphQLUnionType({ + name: 'VerifyPhoneNumberUnion', + description: + 'This union is used with the `verifyPhoneNumber` mutation, allowing for users to verify their phone number, and support any errors that may occur', + types: [verifyPhoneNumberErrorType, verifyPhoneNumberResultType], + resolveType({ _type }) { + if (_type === 'success') { + return verifyPhoneNumberResultType.name + } else { + return verifyPhoneNumberErrorType.name + } + }, +}) diff --git a/api-js/src/validators/__tests__/cleanse-input.test.js b/api/src/validators/__tests__/cleanse-input.test.js similarity index 76% rename from api-js/src/validators/__tests__/cleanse-input.test.js rename to api/src/validators/__tests__/cleanse-input.test.js index 5a3968054b..6f9e40904c 100644 --- a/api-js/src/validators/__tests__/cleanse-input.test.js +++ b/api/src/validators/__tests__/cleanse-input.test.js @@ -3,10 +3,8 @@ import { cleanseInput } from '../index' describe('given an input validate it', () => { describe('string contains symbols', () => { it('returns parsed string', () => { - const testString = '!@#$%^&*()_+-=|{}\\[]<>?,./`:;\'"' - expect(cleanseInput(testString)).toEqual( - '!@#$%^&*()_+-=|{}\[]<>?,./`:;'"', - ) + const testString = '!@#$%^&*()_+-=|{}\\[]<>?,./`:;"' + expect(cleanseInput(testString)).toEqual('!@#$%^&*()_+-=|{}\[]<>?,./`:;"') }) }) describe('input is not given a string', () => { diff --git a/api-js/src/validators/__tests__/decrypt-phone-number.test.js b/api/src/validators/__tests__/decrypt-phone-number.test.js similarity index 90% rename from api-js/src/validators/__tests__/decrypt-phone-number.test.js rename to api/src/validators/__tests__/decrypt-phone-number.test.js index 1105d2a81a..6cc7ca8108 100644 --- a/api-js/src/validators/__tests__/decrypt-phone-number.test.js +++ b/api/src/validators/__tests__/decrypt-phone-number.test.js @@ -1,7 +1,7 @@ -import { decryptPhoneNumber } from '../decrypt-phone-number' +import {decryptPhoneNumber} from '../decrypt-phone-number' import crypto from 'crypto' -const { CIPHER_KEY } = process.env +const {CIPHER_KEY} = process.env describe('given an encrypted phone number field', () => { describe('phone number field is a valid phone number', () => { diff --git a/api-js/src/validators/__tests__/slugify.test.js b/api/src/validators/__tests__/slugify.test.js similarity index 89% rename from api-js/src/validators/__tests__/slugify.test.js rename to api/src/validators/__tests__/slugify.test.js index 5d5e9678bc..c99d00ada3 100644 --- a/api-js/src/validators/__tests__/slugify.test.js +++ b/api/src/validators/__tests__/slugify.test.js @@ -1,5 +1,5 @@ -import { stringify } from 'jest-matcher-utils' -import { slugify } from '../index' +import {stringify} from 'jest-matcher-utils' +import {slugify} from '../index' describe('given a string', () => { it('lowers all characters', () => { diff --git a/api/src/validators/cleanse-input.js b/api/src/validators/cleanse-input.js new file mode 100644 index 0000000000..6f3942df15 --- /dev/null +++ b/api/src/validators/cleanse-input.js @@ -0,0 +1,22 @@ +import validator from 'validator' + +export const cleanseInput = (input) => { + if (typeof input !== 'string' && typeof input !== 'number') { + return '' + } + input = validator.trim(input) + input = validator.stripLow(input) + input = customEscape(input) + return input +} + +const customEscape = (str) => { + return str + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>') + .replace(/\//g, '/') + .replace(/\\/g, '\') + .replace(/`/g, '`') +} diff --git a/api/src/validators/decrypt-phone-number.js b/api/src/validators/decrypt-phone-number.js new file mode 100644 index 0000000000..b80ed1268e --- /dev/null +++ b/api/src/validators/decrypt-phone-number.js @@ -0,0 +1,16 @@ +import crypto from 'crypto' + +const {CIPHER_KEY} = process.env + +export const decryptPhoneNumber = ({iv, tag, phoneNumber: encrypted}) => { + const decipher = crypto.createDecipheriv( + 'aes-256-ccm', + String(CIPHER_KEY), + Buffer.from(iv, 'hex'), + {authTagLength: 16}, + ) + decipher.setAuthTag(Buffer.from(tag, 'hex')) + let decrypted = decipher.update(encrypted, 'hex', 'utf8') + decrypted += decipher.final('utf8') + return decrypted +} diff --git a/api-js/src/validators/index.js b/api/src/validators/index.js similarity index 100% rename from api-js/src/validators/index.js rename to api/src/validators/index.js diff --git a/api-js/src/validators/slugify.js b/api/src/validators/slugify.js similarity index 100% rename from api-js/src/validators/slugify.js rename to api/src/validators/slugify.js diff --git a/api-js/src/summaries/index.js b/api/src/verified-domains/index.js similarity index 100% rename from api-js/src/summaries/index.js rename to api/src/verified-domains/index.js diff --git a/api/src/verified-domains/inputs/__tests__/verified-domain-order.test.js b/api/src/verified-domains/inputs/__tests__/verified-domain-order.test.js new file mode 100644 index 0000000000..a03bd7ef2e --- /dev/null +++ b/api/src/verified-domains/inputs/__tests__/verified-domain-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { verifiedDomainOrder } from '../verified-domain-order' +import { OrderDirection, VerifiedDomainOrderField } from '../../../enums' + +describe('given the verifiedDomainOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = verifiedDomainOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = verifiedDomainOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(VerifiedDomainOrderField)) + }) + }) +}) diff --git a/api-js/src/verified-domains/inputs/index.js b/api/src/verified-domains/inputs/index.js similarity index 100% rename from api-js/src/verified-domains/inputs/index.js rename to api/src/verified-domains/inputs/index.js diff --git a/api-js/src/verified-domains/inputs/verified-domain-order.js b/api/src/verified-domains/inputs/verified-domain-order.js similarity index 81% rename from api-js/src/verified-domains/inputs/verified-domain-order.js rename to api/src/verified-domains/inputs/verified-domain-order.js index f963c57bda..d6d4d8851e 100644 --- a/api-js/src/verified-domains/inputs/verified-domain-order.js +++ b/api/src/verified-domains/inputs/verified-domain-order.js @@ -7,11 +7,11 @@ export const verifiedDomainOrder = new GraphQLInputObjectType({ description: 'Ordering options for verified domain connections.', fields: () => ({ field: { - type: GraphQLNonNull(VerifiedDomainOrderField), + type: new GraphQLNonNull(VerifiedDomainOrderField), description: 'The field to order verified domains by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-by-domain.test.js b/api/src/verified-domains/loaders/__tests__/load-verified-domain-by-domain.test.js similarity index 84% rename from api-js/src/verified-domains/loaders/__tests__/load-verified-domain-by-domain.test.js rename to api/src/verified-domains/loaders/__tests__/load-verified-domain-by-domain.test.js index 2ea0ec2bcc..55ea1176cf 100644 --- a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-by-domain.test.js +++ b/api/src/verified-domains/loaders/__tests__/load-verified-domain-by-domain.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadVerifiedDomainsById } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -22,11 +23,15 @@ describe('given a loadVerifiedDomainsById dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -131,17 +136,13 @@ describe('given a loadVerifiedDomainsById dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedDomainsById({ query: mockedQuery, i18n }) try { await loader.load('domain.ca') } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -162,9 +163,7 @@ describe('given a loadVerifiedDomainsById dataloader', () => { try { await loader.load('domain.ca') } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -190,19 +189,13 @@ describe('given a loadVerifiedDomainsById dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedDomainsById({ query: mockedQuery, i18n }) try { await loader.load('domain.ca') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -223,11 +216,7 @@ describe('given a loadVerifiedDomainsById dataloader', () => { try { await loader.load('domain.ca') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-by-key.test.js b/api/src/verified-domains/loaders/__tests__/load-verified-domain-by-key.test.js similarity index 84% rename from api-js/src/verified-domains/loaders/__tests__/load-verified-domain-by-key.test.js rename to api/src/verified-domains/loaders/__tests__/load-verified-domain-by-key.test.js index 7ef69d24ba..0fc8226a6c 100644 --- a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-by-key.test.js +++ b/api/src/verified-domains/loaders/__tests__/load-verified-domain-by-key.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadVerifiedDomainByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -22,11 +23,15 @@ describe('given a loadVerifiedDomainByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -131,17 +136,13 @@ describe('given a loadVerifiedDomainByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedDomainByKey({ query: mockedQuery, i18n }) try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -162,9 +163,7 @@ describe('given a loadVerifiedDomainByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -190,19 +189,13 @@ describe('given a loadVerifiedDomainByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedDomainByKey({ query: mockedQuery, i18n }) try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -223,11 +216,7 @@ describe('given a loadVerifiedDomainByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js b/api/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js similarity index 93% rename from api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js rename to api/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js index 3904eb8254..c956952d34 100644 --- a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js +++ b/api/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js @@ -1,16 +1,14 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { stringify } from 'jest-matcher-utils' import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' -import { - loadVerifiedDomainConnectionsByOrgId, - loadVerifiedDomainByKey, -} from '../../loaders' +import { loadVerifiedDomainConnectionsByOrgId, loadVerifiedDomainByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -30,18 +28,21 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -104,10 +105,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -150,10 +148,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -196,10 +191,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -241,10 +233,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1106,11 +1095,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `VerifiedDomain` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `VerifiedDomain` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -1135,11 +1120,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `VerifiedDomain` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `VerifiedDomain` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -1211,9 +1192,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnectionsByOrgId({ query, cleanseInput, @@ -1230,11 +1209,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User attempted to have \`first\` set as a ${typeof invalidInput} for: loadVerifiedDomainConnectionsByOrgId.`, @@ -1244,9 +1219,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnectionsByOrgId({ query, cleanseInput, @@ -1263,11 +1236,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User attempted to have \`last\` set as a ${typeof invalidInput} for: loadVerifiedDomainConnectionsByOrgId.`, @@ -1280,9 +1249,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { describe('given a database error', () => { describe('when gathering domain keys that are claimed by orgs that the user has affiliations to', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) const connectionLoader = loadVerifiedDomainConnectionsByOrgId({ query, @@ -1299,9 +1266,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1335,9 +1300,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1438,11 +1401,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`first` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -1467,11 +1426,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -1543,9 +1498,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnectionsByOrgId({ query, cleanseInput, @@ -1563,9 +1516,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -1576,9 +1527,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnectionsByOrgId({ query, cleanseInput, @@ -1596,9 +1545,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -1612,9 +1559,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { describe('given a database error', () => { describe('when gathering domain keys that are claimed by orgs that the user has affiliations to', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) const connectionLoader = loadVerifiedDomainConnectionsByOrgId({ query, @@ -1631,11 +1576,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -1669,11 +1610,7 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js b/api/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js similarity index 92% rename from api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js rename to api/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js index 25597a8529..c5d31a5488 100644 --- a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js +++ b/api/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js @@ -1,16 +1,14 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { stringify } from 'jest-matcher-utils' import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' -import { - loadVerifiedDomainConnections, - loadVerifiedDomainByKey, -} from '../../loaders' +import { loadVerifiedDomainConnections, loadVerifiedDomainByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -30,18 +28,21 @@ describe('given the load domain connection using org id function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -104,10 +105,7 @@ describe('given the load domain connection using org id function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -149,10 +147,7 @@ describe('given the load domain connection using org id function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -194,10 +189,7 @@ describe('given the load domain connection using org id function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -238,10 +230,7 @@ describe('given the load domain connection using org id function', () => { }) const domainLoader = loadVerifiedDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domain._key, - domainTwo._key, - ]) + const expectedDomains = await domainLoader.loadMany([domain._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1084,11 +1073,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `VerifiedDomain` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `VerifiedDomain` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -1112,11 +1097,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `VerifiedDomain` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `VerifiedDomain` connection cannot be less than zero.')) } expect(consoleOutput).toEqual([ @@ -1186,9 +1167,7 @@ describe('given the load domain connection using org id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnections({ query, cleanseInput, @@ -1204,11 +1183,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User attempted to have \`first\` set as a ${typeof invalidInput} for: loadVerifiedDomainConnections.`, @@ -1218,9 +1193,7 @@ describe('given the load domain connection using org id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnections({ query, cleanseInput, @@ -1236,11 +1209,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User attempted to have \`last\` set as a ${typeof invalidInput} for: loadVerifiedDomainConnections.`, @@ -1253,9 +1222,7 @@ describe('given the load domain connection using org id function', () => { describe('given a database error', () => { describe('when gathering domain keys that are claimed by orgs that the user has affiliations to', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) const connectionLoader = loadVerifiedDomainConnections({ query, @@ -1271,9 +1238,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1307,9 +1272,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load verified domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load verified domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1407,11 +1370,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`first` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -1435,11 +1394,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -1509,9 +1464,7 @@ describe('given the load domain connection using org id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnections({ query, cleanseInput, @@ -1528,9 +1481,7 @@ describe('given the load domain connection using org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -1541,9 +1492,7 @@ describe('given the load domain connection using org id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedDomainConnections({ query, cleanseInput, @@ -1560,9 +1509,7 @@ describe('given the load domain connection using org id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -1576,9 +1523,7 @@ describe('given the load domain connection using org id function', () => { describe('given a database error', () => { describe('when gathering domain keys that are claimed by orgs that the user has affiliations to', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database Error Occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database Error Occurred.')) const connectionLoader = loadVerifiedDomainConnections({ query, @@ -1594,11 +1539,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -1631,11 +1572,7 @@ describe('given the load domain connection using org id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api-js/src/verified-domains/loaders/index.js b/api/src/verified-domains/loaders/index.js similarity index 100% rename from api-js/src/verified-domains/loaders/index.js rename to api/src/verified-domains/loaders/index.js diff --git a/api-js/src/verified-domains/loaders/load-verified-domain-by-domain.js b/api/src/verified-domains/loaders/load-verified-domain-by-domain.js similarity index 100% rename from api-js/src/verified-domains/loaders/load-verified-domain-by-domain.js rename to api/src/verified-domains/loaders/load-verified-domain-by-domain.js diff --git a/api-js/src/verified-domains/loaders/load-verified-domain-by-key.js b/api/src/verified-domains/loaders/load-verified-domain-by-key.js similarity index 100% rename from api-js/src/verified-domains/loaders/load-verified-domain-by-key.js rename to api/src/verified-domains/loaders/load-verified-domain-by-key.js diff --git a/api-js/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js b/api/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js similarity index 100% rename from api-js/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js rename to api/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js diff --git a/api-js/src/verified-domains/loaders/load-verified-domain-connections.js b/api/src/verified-domains/loaders/load-verified-domain-connections.js similarity index 100% rename from api-js/src/verified-domains/loaders/load-verified-domain-connections.js rename to api/src/verified-domains/loaders/load-verified-domain-connections.js diff --git a/api-js/src/verified-domains/objects/__tests__/verified-domain-connection.test.js b/api/src/verified-domains/objects/__tests__/verified-domain-connection.test.js similarity index 100% rename from api-js/src/verified-domains/objects/__tests__/verified-domain-connection.test.js rename to api/src/verified-domains/objects/__tests__/verified-domain-connection.test.js diff --git a/api-js/src/verified-domains/objects/__tests__/verified-domain.test.js b/api/src/verified-domains/objects/__tests__/verified-domain.test.js similarity index 85% rename from api-js/src/verified-domains/objects/__tests__/verified-domain.test.js rename to api/src/verified-domains/objects/__tests__/verified-domain.test.js index b3dd32d579..f001a14da0 100644 --- a/api-js/src/verified-domains/objects/__tests__/verified-domain.test.js +++ b/api/src/verified-domains/objects/__tests__/verified-domain.test.js @@ -1,6 +1,6 @@ import { GraphQLNonNull, GraphQLID } from 'graphql' import { toGlobalId } from 'graphql-relay' -import { GraphQLDate } from 'graphql-scalars' +import { GraphQLDateTime } from 'graphql-scalars' import { verifiedOrganizationConnection } from '../../../verified-organizations/objects' import { verifiedDomainType } from '../index' @@ -13,7 +13,7 @@ describe('given the verified domains object', () => { const demoType = verifiedDomainType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a domain field', () => { const demoType = verifiedDomainType.getFields() @@ -25,7 +25,7 @@ describe('given the verified domains object', () => { const demoType = verifiedDomainType.getFields() expect(demoType).toHaveProperty('lastRan') - expect(demoType.lastRan.type).toMatchObject(GraphQLDate) + expect(demoType.lastRan.type).toMatchObject(GraphQLDateTime) }) it('has a status field', () => { const demoType = verifiedDomainType.getFields() @@ -37,9 +37,7 @@ describe('given the verified domains object', () => { const demoType = verifiedDomainType.getFields() expect(demoType).toHaveProperty('organizations') - expect(demoType.organizations.type).toMatchObject( - verifiedOrganizationConnection.connectionType, - ) + expect(demoType.organizations.type).toMatchObject(verifiedOrganizationConnection.connectionType) }) }) @@ -48,27 +46,21 @@ describe('given the verified domains object', () => { it('returns the resolved value', () => { const demoType = verifiedDomainType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('verifiedDomain', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('verifiedDomain', 1)) }) }) describe('testing the domain resolver', () => { it('returns the resolved value', () => { const demoType = verifiedDomainType.getFields() - expect(demoType.domain.resolve({ domain: 'test.gc.ca' })).toEqual( - 'test.gc.ca', - ) + expect(demoType.domain.resolve({ domain: 'test.gc.ca' })).toEqual('test.gc.ca') }) }) describe('testing the lastRan resolver', () => { it('returns the resolved value', () => { const demoType = verifiedDomainType.getFields() - expect( - demoType.lastRan.resolve({ lastRan: '2020-10-02T12:43:39Z' }), - ).toEqual('2020-10-02T12:43:39Z') + expect(demoType.lastRan.resolve({ lastRan: '2020-10-02T12:43:39Z' })).toEqual('2020-10-02T12:43:39Z') }) }) describe('testing the status resolver', () => { @@ -146,9 +138,7 @@ describe('given the verified domains object', () => { { first: 1 }, { loaders: { - loadVerifiedOrgConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), + loadVerifiedOrgConnectionsByDomainId: jest.fn().mockReturnValue(expectedResult), }, }, ), diff --git a/api-js/src/verified-domains/objects/index.js b/api/src/verified-domains/objects/index.js similarity index 100% rename from api-js/src/verified-domains/objects/index.js rename to api/src/verified-domains/objects/index.js diff --git a/api-js/src/verified-domains/objects/verified-domain-connection.js b/api/src/verified-domains/objects/verified-domain-connection.js similarity index 100% rename from api-js/src/verified-domains/objects/verified-domain-connection.js rename to api/src/verified-domains/objects/verified-domain-connection.js diff --git a/api-js/src/verified-domains/objects/verified-domain.js b/api/src/verified-domains/objects/verified-domain.js similarity index 83% rename from api-js/src/verified-domains/objects/verified-domain.js rename to api/src/verified-domains/objects/verified-domain.js index 624ce489e7..281eba3768 100644 --- a/api-js/src/verified-domains/objects/verified-domain.js +++ b/api/src/verified-domains/objects/verified-domain.js @@ -1,6 +1,6 @@ import { GraphQLObjectType } from 'graphql' import { connectionArgs, globalIdField } from 'graphql-relay' -import { GraphQLDate } from 'graphql-scalars' +import { GraphQLDateTime } from 'graphql-scalars' import { domainStatus } from '../../domain/objects' import { Domain } from '../../scalars' @@ -18,7 +18,7 @@ export const verifiedDomainType = new GraphQLObjectType({ resolve: ({ domain }) => domain, }, lastRan: { - type: GraphQLDate, + type: GraphQLDateTime, description: 'The last time that a scan was ran on this domain.', resolve: ({ lastRan }) => lastRan, }, @@ -32,17 +32,12 @@ export const verifiedDomainType = new GraphQLObjectType({ args: { orderBy: { type: verifiedOrganizationOrder, - description: - 'Ordering options for verified organization connections.', + description: 'Ordering options for verified organization connections.', }, ...connectionArgs, }, description: 'The organization that this domain belongs to.', - resolve: async ( - { _id }, - args, - { loaders: { loadVerifiedOrgConnectionsByDomainId } }, - ) => { + resolve: async ({ _id }, args, { loaders: { loadVerifiedOrgConnectionsByDomainId } }) => { const orgs = await loadVerifiedOrgConnectionsByDomainId({ domainId: _id, ...args, diff --git a/api-js/src/verified-domains/queries/__tests__/find-verified-domain-by-domain.test.js b/api/src/verified-domains/queries/__tests__/find-verified-domain-by-domain.test.js similarity index 83% rename from api-js/src/verified-domains/queries/__tests__/find-verified-domain-by-domain.test.js rename to api/src/verified-domains/queries/__tests__/find-verified-domain-by-domain.test.js index e73a03f242..c9a4463a0a 100644 --- a/api-js/src/verified-domains/queries/__tests__/find-verified-domain-by-domain.test.js +++ b/api/src/verified-domains/queries/__tests__/find-verified-domain-by-domain.test.js @@ -1,16 +1,17 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadVerifiedOrgConnectionsByDomainId } from '../../../verified-organizations/loaders' import { loadVerifiedDomainsById } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -51,11 +52,15 @@ describe('given findVerifiedDomainByDomain query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -87,7 +92,7 @@ describe('given findVerifiedDomainByDomain query', () => { domain = await collections.domains.save({ domain: 'test.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -108,17 +113,17 @@ describe('given findVerifiedDomainByDomain query', () => { await drop() }) it('returns domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedDomainByDomain(domain: "test.gc.ca") { id } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -126,11 +131,10 @@ describe('given findVerifiedDomainByDomain query', () => { }, loaders: { loadVerifiedDomainsById: loadVerifiedDomainsById({ query }), - loadVerifiedOrgConnectionsByDomainId: - loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), + loadVerifiedOrgConnectionsByDomainId: loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), }, }, - ) + }) const expectedResponse = { data: { @@ -161,17 +165,17 @@ describe('given findVerifiedDomainByDomain query', () => { describe('given unsuccessful domain retrieval', () => { describe('domain cannot be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedDomainByDomain(domain: "not-test.gc.ca") { id } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -183,13 +187,9 @@ describe('given findVerifiedDomainByDomain query', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - `No verified domain with the provided domain could be found.`, - ), - ] + const error = [new GraphQLError(`No verified domain with the provided domain could be found.`)] expect(response.errors).toEqual(error) }) @@ -214,17 +214,17 @@ describe('given findVerifiedDomainByDomain query', () => { describe('given unsuccessful domain retrieval', () => { describe('domain cannot be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedDomainByDomain(domain: "not-test.gc.ca") { id } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -236,13 +236,9 @@ describe('given findVerifiedDomainByDomain query', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - `Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.`, - ), - ] + const error = [new GraphQLError(`Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.`)] expect(response.errors).toEqual(error) }) diff --git a/api-js/src/verified-domains/queries/__tests__/find-verified-domains.test.js b/api/src/verified-domains/queries/__tests__/find-verified-domains.test.js similarity index 88% rename from api-js/src/verified-domains/queries/__tests__/find-verified-domains.test.js rename to api/src/verified-domains/queries/__tests__/find-verified-domains.test.js index 999fdde21a..6857181e3b 100644 --- a/api-js/src/verified-domains/queries/__tests__/find-verified-domains.test.js +++ b/api/src/verified-domains/queries/__tests__/find-verified-domains.test.js @@ -1,16 +1,17 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadVerifiedOrgConnectionsByDomainId } from '../../../verified-organizations' import { loadVerifiedDomainConnections } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -51,11 +52,15 @@ describe('given findVerifiedDomains query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -87,7 +92,7 @@ describe('given findVerifiedDomains query', () => { domain = await collections.domains.save({ domain: 'test.gc.ca', lastRan: null, - selectors: ['selector1._domainkey', 'selector2._domainkey'], + selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -108,9 +113,9 @@ describe('given findVerifiedDomains query', () => { await drop() }) it('returns domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedDomains(first: 5) { edges { @@ -129,8 +134,8 @@ describe('given findVerifiedDomains query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -141,11 +146,10 @@ describe('given findVerifiedDomains query', () => { query, cleanseInput, }), - loadVerifiedOrgConnectionsByDomainId: - loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), + loadVerifiedOrgConnectionsByDomainId: loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), }, }, - ) + }) const expectedResponse = { data: { @@ -175,9 +179,9 @@ describe('given findVerifiedDomains query', () => { describe('given unsuccessful domain retrieval', () => { describe('domain cannot be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedDomains(first: 5) { edges { @@ -196,8 +200,8 @@ describe('given findVerifiedDomains query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -216,7 +220,7 @@ describe('given findVerifiedDomains query', () => { }), }, }, - ) + }) const expectedResponse = { data: { diff --git a/api/src/verified-domains/queries/find-verified-domain-by-domain.js b/api/src/verified-domains/queries/find-verified-domain-by-domain.js new file mode 100644 index 0000000000..0100b1d4a0 --- /dev/null +++ b/api/src/verified-domains/queries/find-verified-domain-by-domain.js @@ -0,0 +1,30 @@ +import { GraphQLNonNull } from 'graphql' +import { t } from '@lingui/macro' +import { Domain } from '../../scalars' +import { verifiedDomainType } from '../objects' + +export const findVerifiedDomainByDomain = { + type: verifiedDomainType, + description: 'Retrieve a specific verified domain by providing a domain.', + args: { + domain: { + type: new GraphQLNonNull(Domain), + description: 'The domain you wish to retrieve information for.', + }, + }, + resolve: async (_, args, { i18n, loaders: { loadVerifiedDomainsById }, validators: { cleanseInput } }) => { + // Cleanse input + const domainInput = cleanseInput(args.domain) + + // Retrieve domain by domain + const domain = await loadVerifiedDomainsById.load(domainInput) + + if (typeof domain === 'undefined') { + console.warn(`User could not retrieve verified domain.`) + throw new Error(i18n._(t`No verified domain with the provided domain could be found.`)) + } + + domain.id = domain._key + return domain + }, +} diff --git a/api-js/src/verified-domains/queries/find-verified-domains.js b/api/src/verified-domains/queries/find-verified-domains.js similarity index 100% rename from api-js/src/verified-domains/queries/find-verified-domains.js rename to api/src/verified-domains/queries/find-verified-domains.js diff --git a/api-js/src/verified-domains/queries/index.js b/api/src/verified-domains/queries/index.js similarity index 100% rename from api-js/src/verified-domains/queries/index.js rename to api/src/verified-domains/queries/index.js diff --git a/api-js/src/verified-domains/index.js b/api/src/verified-organizations/index.js similarity index 100% rename from api-js/src/verified-domains/index.js rename to api/src/verified-organizations/index.js diff --git a/api/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js b/api/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js new file mode 100644 index 0000000000..b0123540a1 --- /dev/null +++ b/api/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js @@ -0,0 +1,21 @@ +import { GraphQLNonNull } from 'graphql' + +import { verifiedOrganizationOrder } from '../verified-organization-order' +import { OrderDirection, VerifiedOrganizationOrderField } from '../../../enums' + +describe('given the verifiedOrganizationOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = verifiedOrganizationOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject(new GraphQLNonNull(OrderDirection)) + }) + it('has a field field', () => { + const demoType = verifiedOrganizationOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(VerifiedOrganizationOrderField)) + }) + }) +}) diff --git a/api-js/src/verified-organizations/inputs/index.js b/api/src/verified-organizations/inputs/index.js similarity index 100% rename from api-js/src/verified-organizations/inputs/index.js rename to api/src/verified-organizations/inputs/index.js diff --git a/api-js/src/verified-organizations/inputs/verified-organization-order.js b/api/src/verified-organizations/inputs/verified-organization-order.js similarity index 82% rename from api-js/src/verified-organizations/inputs/verified-organization-order.js rename to api/src/verified-organizations/inputs/verified-organization-order.js index cd2f11ccb7..dff98ecd79 100644 --- a/api-js/src/verified-organizations/inputs/verified-organization-order.js +++ b/api/src/verified-organizations/inputs/verified-organization-order.js @@ -7,11 +7,11 @@ export const verifiedOrganizationOrder = new GraphQLInputObjectType({ description: 'Ordering options for verified organization connections.', fields: () => ({ field: { - type: GraphQLNonNull(VerifiedOrganizationOrderField), + type: new GraphQLNonNull(VerifiedOrganizationOrderField), description: 'The field to order verified organizations by.', }, direction: { - type: GraphQLNonNull(OrderDirection), + type: new GraphQLNonNull(OrderDirection), description: 'The ordering direction.', }, }), diff --git a/api-js/src/verified-organizations/loaders/__tests__/load-verified-org-conn-by-domain-id.test.js b/api/src/verified-organizations/loaders/__tests__/load-verified-org-conn-by-domain-id.test.js similarity index 79% rename from api-js/src/verified-organizations/loaders/__tests__/load-verified-org-conn-by-domain-id.test.js rename to api/src/verified-organizations/loaders/__tests__/load-verified-org-conn-by-domain-id.test.js index ff73a766bc..b7ad6383d9 100644 --- a/api-js/src/verified-organizations/loaders/__tests__/load-verified-org-conn-by-domain-id.test.js +++ b/api/src/verified-organizations/loaders/__tests__/load-verified-org-conn-by-domain-id.test.js @@ -1,16 +1,14 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' -import { - loadVerifiedOrgConnectionsByDomainId, - loadVerifiedOrgByKey, -} from '../../loaders' +import { loadVerifiedOrgConnectionsByDomainId, loadVerifiedOrgByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -163,23 +161,19 @@ describe('given the load organizations connection function', () => { console.error = mockedError console.warn = mockedWarn - let query, - drop, - truncate, - collections, - org, - orgTwo, - domain, - domainTwo, - domainThree + let query, drop, truncate, collections, org, orgTwo, domain, domainTwo, domainThree beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -245,10 +239,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), node: { ...expectedOrgs[1], }, @@ -258,14 +249,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), }, } @@ -279,23 +264,19 @@ describe('given the load organizations connection function', () => { console.error = mockedError console.warn = mockedWarn - let query, - drop, - truncate, - collections, - org, - orgTwo, - domain, - domainTwo, - domainThree + let query, drop, truncate, collections, org, orgTwo, domain, domainTwo, domainThree beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) org = await collections.organizations.save(orgOneData) orgTwo = await collections.organizations.save(orgTwoData) @@ -359,10 +340,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), node: { ...expectedOrgs[0], }, @@ -372,14 +350,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), }, } @@ -393,23 +365,19 @@ describe('given the load organizations connection function', () => { console.error = mockedError console.warn = mockedWarn - let query, - drop, - truncate, - collections, - org, - orgTwo, - domain, - domainTwo, - domainThree + let query, drop, truncate, collections, org, orgTwo, domain, domainTwo, domainThree beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) org = await collections.organizations.save(orgOneData) orgTwo = await collections.organizations.save(orgTwoData) @@ -472,10 +440,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), node: { ...expectedOrgs[0], }, @@ -485,14 +450,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), }, } @@ -506,23 +465,19 @@ describe('given the load organizations connection function', () => { console.error = mockedError console.warn = mockedWarn - let query, - drop, - truncate, - collections, - org, - orgTwo, - domain, - domainTwo, - domainThree + let query, drop, truncate, collections, org, orgTwo, domain, domainTwo, domainThree beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: 'limit_' + dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) org = await collections.organizations.save(orgOneData) orgTwo = await collections.organizations.save(orgTwoData) @@ -585,10 +540,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), node: { ...expectedOrgs[1], }, @@ -598,14 +550,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), }, } @@ -619,24 +565,19 @@ describe('given the load organizations connection function', () => { console.error = mockedError console.warn = mockedWarn - let query, - drop, - truncate, - collections, - org, - orgTwo, - domain, - domainTwo, - domainThree, - orgThree + let query, drop, truncate, collections, org, orgTwo, domain, domainTwo, domainThree, orgThree beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: 'ordrby_' + dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) org = await collections.organizations.save(orgOneData) @@ -718,10 +659,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -731,14 +669,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -778,10 +710,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -791,14 +720,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -840,10 +763,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -853,14 +773,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -900,10 +814,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -913,14 +824,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -962,10 +867,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -975,14 +877,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1022,10 +918,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1035,14 +928,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1084,10 +971,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1097,14 +981,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1145,10 +1023,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1158,14 +1033,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1207,10 +1076,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1220,14 +1086,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1267,10 +1127,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1280,14 +1137,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1329,10 +1180,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1342,14 +1190,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1389,10 +1231,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1402,14 +1241,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1451,10 +1284,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1464,14 +1294,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1511,10 +1335,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1524,14 +1345,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1573,10 +1388,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1586,14 +1398,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1633,10 +1439,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1646,14 +1449,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1695,10 +1492,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1708,14 +1502,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1755,10 +1543,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1768,14 +1553,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1817,10 +1596,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1830,14 +1606,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1877,10 +1647,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1890,14 +1657,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1940,10 +1701,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1953,14 +1711,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2000,10 +1752,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2013,14 +1762,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2062,10 +1805,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2075,14 +1815,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2123,10 +1857,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2136,14 +1867,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2157,11 +1882,15 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { ;({ query, drop } = await ensure({ - type: 'database', - name: 'no_org_' + dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) @@ -2246,9 +1975,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User did not have either `first` or `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User did not have either `first` or `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2286,9 +2013,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `first` and `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `first` and `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2317,17 +2042,11 @@ describe('given the load organizations connection function', () => { first: -1, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` on the `VerifiedOrganization` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`first` on the `VerifiedOrganization` connection cannot be less than zero.')) } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `first` set below zero for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `first` set below zero for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2355,17 +2074,11 @@ describe('given the load organizations connection function', () => { last: -1, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` on the `VerifiedOrganization` connection cannot be less than zero.', - ), - ) + expect(err).toEqual(new Error('`last` on the `VerifiedOrganization` connection cannot be less than zero.')) } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `last` set below zero for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `last` set below zero for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2402,9 +2115,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `first` to 101 for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `first` to 101 for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2441,9 +2152,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `last` to 101 for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `last` to 101 for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2469,19 +2178,13 @@ describe('given the load organizations connection function', () => { afterAll(() => { console.warn = warn }) - it(`returns an error when first set is to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set is to ${stringify(invalidInput)}`, async () => { try { await connectionLoader({ first: invalidInput, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(console.warn.mock.calls).toEqual([ [ @@ -2512,19 +2215,13 @@ describe('given the load organizations connection function', () => { afterAll(() => { console.warn = warn }) - it(`returns an error when last is set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last is set to ${stringify(invalidInput)}`, async () => { try { await connectionLoader({ last: invalidInput, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(console.warn.mock.calls).toEqual([ [ @@ -2542,9 +2239,7 @@ describe('given the load organizations connection function', () => { console.error = jest.fn() const connectionLoader = loadVerifiedOrgConnectionsByDomainId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), language: 'en', cleanseInput, i18n, @@ -2555,9 +2250,7 @@ describe('given the load organizations connection function', () => { domainId: '1234', first: 5, }), - ).rejects.toThrow( - 'Unable to load verified organization(s). Please try again.', - ) + ).rejects.toThrow('Unable to load verified organization(s). Please try again.') expect(console.error.mock.calls).toEqual([ [ @@ -2590,9 +2283,7 @@ describe('given the load organizations connection function', () => { domainId: '1234', first: 5, }), - ).rejects.toThrow( - 'Unable to load verified organization(s). Please try again.', - ) + ).rejects.toThrow('Unable to load verified organization(s). Please try again.') expect(console.error.mock.calls).toEqual([ [ @@ -2650,9 +2341,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User did not have either `first` or `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User did not have either `first` or `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2690,9 +2379,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `first` and `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `first` and `last` arguments set for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2722,16 +2409,12 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.'), ) } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `first` set below zero for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `first` set below zero for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2760,16 +2443,12 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`last` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.', - ), + new Error('`last` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.'), ) } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `last` set below zero for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `last` set below zero for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2806,9 +2485,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `first` to 101 for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `first` to 101 for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2845,9 +2522,7 @@ describe('given the load organizations connection function', () => { } expect(console.warn.mock.calls).toEqual([ - [ - 'User attempted to have `last` to 101 for: loadVerifiedOrgConnectionsByDomainId.', - ], + ['User attempted to have `last` to 101 for: loadVerifiedOrgConnectionsByDomainId.'], ]) }) }) @@ -2873,18 +2548,14 @@ describe('given the load organizations connection function', () => { afterAll(() => { console.warn = warn }) - it(`returns an error when first set is to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set is to ${stringify(invalidInput)}`, async () => { try { await connectionLoader({ first: invalidInput, }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(console.warn.mock.calls).toEqual([ @@ -2916,18 +2587,14 @@ describe('given the load organizations connection function', () => { afterAll(() => { console.warn = warn }) - it(`returns an error when last is set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last is set to ${stringify(invalidInput)}`, async () => { try { await connectionLoader({ last: invalidInput, }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(console.warn.mock.calls).toEqual([ @@ -2946,9 +2613,7 @@ describe('given the load organizations connection function', () => { console.error = jest.fn() const connectionLoader = loadVerifiedOrgConnectionsByDomainId({ - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), language: 'en', cleanseInput, i18n, @@ -2959,9 +2624,7 @@ describe('given the load organizations connection function', () => { domainId: '1234', first: 5, }), - ).rejects.toThrow( - 'Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.', - ) + ).rejects.toThrow('Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.') expect(console.error.mock.calls).toEqual([ [ @@ -2994,9 +2657,7 @@ describe('given the load organizations connection function', () => { domainId: '1234', first: 5, }), - ).rejects.toThrow( - 'Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.', - ) + ).rejects.toThrow('Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.') expect(console.error.mock.calls).toEqual([ [ diff --git a/api-js/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js b/api/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js similarity index 79% rename from api-js/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js rename to api/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js index ec2b3439ef..95d76034cb 100644 --- a/api-js/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js +++ b/api/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js @@ -1,27 +1,19 @@ import { stringify } from 'jest-matcher-utils' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { cleanseInput } from '../../../validators' import { loadVerifiedOrgConnections, loadVerifiedOrgByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the load organizations connection function', () => { - let query, - drop, - truncate, - collections, - org, - orgTwo, - i18n, - domain, - domainTwo, - domainThree + let query, drop, truncate, collections, org, orgTwo, i18n, domain, domainTwo, domainThree const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -37,11 +29,15 @@ describe('given the load organizations connection function', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -192,10 +188,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), node: { ...expectedOrgs[1], }, @@ -205,14 +198,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), }, } @@ -245,10 +232,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), node: { ...expectedOrgs[0], }, @@ -258,14 +242,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), }, } @@ -297,10 +275,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), node: { ...expectedOrgs[0], }, @@ -310,14 +285,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), }, } @@ -349,10 +318,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), node: { ...expectedOrgs[1], }, @@ -362,14 +328,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), }, } @@ -458,10 +418,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -471,14 +428,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -517,10 +468,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -530,14 +478,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -578,10 +520,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -591,14 +530,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -637,10 +570,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -650,14 +580,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -698,10 +622,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -711,14 +632,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -757,10 +672,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -770,14 +682,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -818,10 +724,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -831,14 +734,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -877,10 +774,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -890,14 +784,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -938,10 +826,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -951,14 +836,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -997,10 +876,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1010,14 +886,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1058,10 +928,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1071,14 +938,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1117,10 +978,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1130,14 +988,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1178,10 +1030,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1191,14 +1040,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1237,10 +1080,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1250,14 +1090,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1298,10 +1132,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1311,14 +1142,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1357,10 +1182,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1370,14 +1192,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1418,10 +1234,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1431,14 +1244,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1477,10 +1284,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1490,14 +1294,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1538,10 +1336,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1551,14 +1346,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1597,10 +1386,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1610,14 +1396,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1658,10 +1438,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1671,14 +1448,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1717,10 +1488,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1730,14 +1498,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1778,10 +1540,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1791,14 +1550,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1837,10 +1590,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1850,14 +1600,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -1940,10 +1684,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), node: { ...expectedOrgs[1], }, @@ -1953,14 +1694,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), }, } @@ -1993,10 +1728,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), node: { ...expectedOrgs[0], }, @@ -2006,14 +1738,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), }, } @@ -2045,10 +1771,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), node: { ...expectedOrgs[0], }, @@ -2058,14 +1781,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: false, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[0]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[0]._key), }, } @@ -2097,10 +1814,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), node: { ...expectedOrgs[1], }, @@ -2110,14 +1824,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: false, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrgs[1]._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrgs[1]._key), }, } @@ -2206,10 +1914,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2219,14 +1924,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2265,10 +1964,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2278,14 +1974,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2326,10 +2016,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2339,14 +2026,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2385,10 +2066,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2398,14 +2076,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2446,10 +2118,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2459,14 +2128,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2505,10 +2168,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2518,14 +2178,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2566,10 +2220,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2579,14 +2230,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2625,10 +2270,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2638,14 +2280,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2686,10 +2322,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2699,14 +2332,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2745,10 +2372,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2758,14 +2382,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2806,10 +2424,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2819,14 +2434,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2865,10 +2474,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2878,14 +2484,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2926,10 +2526,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2939,14 +2536,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -2985,10 +2576,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2998,14 +2586,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3046,10 +2628,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3059,14 +2638,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3105,10 +2678,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3118,14 +2688,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3166,10 +2730,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3179,14 +2740,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3225,10 +2780,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3238,14 +2790,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3286,10 +2832,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3299,14 +2842,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3345,10 +2882,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3358,14 +2892,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3406,10 +2934,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3419,14 +2944,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3465,10 +2984,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3478,14 +2994,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3526,10 +3036,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3539,14 +3046,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3585,10 +3086,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3598,14 +3096,8 @@ describe('given the load organizations connection function', () => { pageInfo: { hasNextPage: true, hasPreviousPage: true, - startCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), - endCursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + startCursor: toGlobalId('verifiedOrganization', expectedOrg._key), + endCursor: toGlobalId('verifiedOrganization', expectedOrg._key), }, } @@ -3736,9 +3228,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` on the `VerifiedOrganization` connection cannot be less than zero.', - ), + new Error('`first` on the `VerifiedOrganization` connection cannot be less than zero.'), ) } @@ -3765,9 +3255,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`last` on the `VerifiedOrganization` connection cannot be less than zero.', - ), + new Error('`last` on the `VerifiedOrganization` connection cannot be less than zero.'), ) } @@ -3802,9 +3290,7 @@ describe('given the load organizations connection function', () => { ) } - expect(consoleOutput).toEqual([ - 'User attempted to have `first` to 101 for: loadVerifiedOrgConnections.', - ]) + expect(consoleOutput).toEqual(['User attempted to have `first` to 101 for: loadVerifiedOrgConnections.']) }) }) describe('last is set', () => { @@ -3831,18 +3317,14 @@ describe('given the load organizations connection function', () => { ) } - expect(consoleOutput).toEqual([ - 'User attempted to have `last` to 101 for: loadVerifiedOrgConnections.', - ]) + expect(consoleOutput).toEqual(['User attempted to have `last` to 101 for: loadVerifiedOrgConnections.']) }) }) }) describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedOrgConnections({ query, language: 'en', @@ -3859,11 +3341,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User attempted to have \`first\` set as a ${typeof invalidInput} for: loadVerifiedOrgConnections.`, @@ -3873,9 +3351,7 @@ describe('given the load organizations connection function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedOrgConnections({ query, language: 'en', @@ -3892,11 +3368,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User attempted to have \`last\` set as a ${typeof invalidInput} for: loadVerifiedOrgConnections.`, @@ -3909,9 +3381,7 @@ describe('given the load organizations connection function', () => { describe('given a database error', () => { describe('when gathering organizations', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadVerifiedOrgConnections({ query, @@ -3926,11 +3396,7 @@ describe('given the load organizations connection function', () => { } await connectionLoader({ ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load verified organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load verified organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -3963,11 +3429,7 @@ describe('given the load organizations connection function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to load verified organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to load verified organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -4066,9 +3528,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`first` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.', - ), + new Error('`first` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.'), ) } @@ -4095,9 +3555,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - '`last` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.', - ), + new Error('`last` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro.'), ) } @@ -4132,9 +3590,7 @@ describe('given the load organizations connection function', () => { ) } - expect(consoleOutput).toEqual([ - 'User attempted to have `first` to 101 for: loadVerifiedOrgConnections.', - ]) + expect(consoleOutput).toEqual(['User attempted to have `first` to 101 for: loadVerifiedOrgConnections.']) }) }) describe('last is set', () => { @@ -4161,18 +3617,14 @@ describe('given the load organizations connection function', () => { ) } - expect(consoleOutput).toEqual([ - 'User attempted to have `last` to 101 for: loadVerifiedOrgConnections.', - ]) + expect(consoleOutput).toEqual(['User attempted to have `last` to 101 for: loadVerifiedOrgConnections.']) }) }) }) describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedOrgConnections({ query, language: 'fr', @@ -4190,9 +3642,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -4203,9 +3653,7 @@ describe('given the load organizations connection function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadVerifiedOrgConnections({ query, language: 'fr', @@ -4223,9 +3671,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -4239,9 +3685,7 @@ describe('given the load organizations connection function', () => { describe('given a database error', () => { describe('when gathering organizations', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const connectionLoader = loadVerifiedOrgConnections({ query, @@ -4256,11 +3700,7 @@ describe('given the load organizations connection function', () => { } await connectionLoader({ ...connectionArgs }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ @@ -4294,9 +3734,7 @@ describe('given the load organizations connection function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.', - ), + new Error('Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.'), ) } diff --git a/api-js/src/verified-organizations/loaders/__tests__/load-verified-organization-by-key.test.js b/api/src/verified-organizations/loaders/__tests__/load-verified-organization-by-key.test.js similarity index 90% rename from api-js/src/verified-organizations/loaders/__tests__/load-verified-organization-by-key.test.js rename to api/src/verified-organizations/loaders/__tests__/load-verified-organization-by-key.test.js index 8f92f93153..d4564b6ad5 100644 --- a/api-js/src/verified-organizations/loaders/__tests__/load-verified-organization-by-key.test.js +++ b/api/src/verified-organizations/loaders/__tests__/load-verified-organization-by-key.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadVerifiedOrgByKey } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,11 +24,15 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -239,9 +244,7 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedOrgByKey({ query: mockedQuery, language: 'en', @@ -251,11 +254,7 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find verified organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to find verified organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -280,11 +279,7 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { try { await loader.load('1') } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find verified organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to find verified organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -310,9 +305,7 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedOrgByKey({ query: mockedQuery, language: 'fr', @@ -323,9 +316,7 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { await loader.load('1') } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.', - ), + new Error('Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.'), ) } @@ -352,9 +343,7 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { await loader.load('1') } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.', - ), + new Error('Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.'), ) } diff --git a/api-js/src/verified-organizations/loaders/__tests__/load-verified-organization-by-slug.test.js b/api/src/verified-organizations/loaders/__tests__/load-verified-organization-by-slug.test.js similarity index 87% rename from api-js/src/verified-organizations/loaders/__tests__/load-verified-organization-by-slug.test.js rename to api/src/verified-organizations/loaders/__tests__/load-verified-organization-by-slug.test.js index 2553726599..446f68518f 100644 --- a/api-js/src/verified-organizations/loaders/__tests__/load-verified-organization-by-slug.test.js +++ b/api/src/verified-organizations/loaders/__tests__/load-verified-organization-by-slug.test.js @@ -1,10 +1,11 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { loadVerifiedOrgBySlug } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -23,11 +24,15 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -239,9 +244,7 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedOrgBySlug({ query: mockedQuery, language: 'en', @@ -251,11 +254,7 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { try { await loader.load('slug') } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find verified organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to find verified organization(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -280,16 +279,10 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { try { await loader.load('slug') } catch (err) { - expect(err).toEqual( - new Error( - 'Unable to find verified organization(s). Please try again.', - ), - ) + expect(err).toEqual(new Error('Unable to find verified organization(s). Please try again.')) } - expect(consoleOutput).toEqual([ - `Cursor error during loadVerifiedOrgBySlug: Error: Cursor error occurred.`, - ]) + expect(consoleOutput).toEqual([`Cursor error during loadVerifiedOrgBySlug: Error: Cursor error occurred.`]) }) }) }) @@ -310,9 +303,7 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { }) describe('database error is raised', () => { it('returns an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) const loader = loadVerifiedOrgBySlug({ query: mockedQuery, language: 'fr', @@ -323,9 +314,7 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { await loader.load('slug') } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.', - ), + new Error('Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.'), ) } @@ -352,15 +341,11 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { await loader.load('slug') } catch (err) { expect(err).toEqual( - new Error( - 'Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.', - ), + new Error('Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.'), ) } - expect(consoleOutput).toEqual([ - `Cursor error during loadVerifiedOrgBySlug: Error: Cursor error occurred.`, - ]) + expect(consoleOutput).toEqual([`Cursor error during loadVerifiedOrgBySlug: Error: Cursor error occurred.`]) }) }) }) diff --git a/api-js/src/verified-organizations/loaders/index.js b/api/src/verified-organizations/loaders/index.js similarity index 100% rename from api-js/src/verified-organizations/loaders/index.js rename to api/src/verified-organizations/loaders/index.js diff --git a/api-js/src/verified-organizations/loaders/load-verified-organization-by-key.js b/api/src/verified-organizations/loaders/load-verified-organization-by-key.js similarity index 100% rename from api-js/src/verified-organizations/loaders/load-verified-organization-by-key.js rename to api/src/verified-organizations/loaders/load-verified-organization-by-key.js diff --git a/api-js/src/verified-organizations/loaders/load-verified-organization-by-slug.js b/api/src/verified-organizations/loaders/load-verified-organization-by-slug.js similarity index 100% rename from api-js/src/verified-organizations/loaders/load-verified-organization-by-slug.js rename to api/src/verified-organizations/loaders/load-verified-organization-by-slug.js diff --git a/api-js/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js b/api/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js similarity index 100% rename from api-js/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js rename to api/src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js diff --git a/api-js/src/verified-organizations/loaders/load-verified-organizations-connections.js b/api/src/verified-organizations/loaders/load-verified-organizations-connections.js similarity index 100% rename from api-js/src/verified-organizations/loaders/load-verified-organizations-connections.js rename to api/src/verified-organizations/loaders/load-verified-organizations-connections.js diff --git a/api-js/src/verified-organizations/objects/__tests__/verified-organization-connection.test.js b/api/src/verified-organizations/objects/__tests__/verified-organization-connection.test.js similarity index 100% rename from api-js/src/verified-organizations/objects/__tests__/verified-organization-connection.test.js rename to api/src/verified-organizations/objects/__tests__/verified-organization-connection.test.js diff --git a/api-js/src/verified-organizations/objects/__tests__/verified-organization.test.js b/api/src/verified-organizations/objects/__tests__/verified-organization.test.js similarity index 91% rename from api-js/src/verified-organizations/objects/__tests__/verified-organization.test.js rename to api/src/verified-organizations/objects/__tests__/verified-organization.test.js index e747a198ea..a626f92b39 100644 --- a/api-js/src/verified-organizations/objects/__tests__/verified-organization.test.js +++ b/api/src/verified-organizations/objects/__tests__/verified-organization.test.js @@ -1,10 +1,4 @@ -import { - GraphQLInt, - GraphQLBoolean, - GraphQLString, - GraphQLNonNull, - GraphQLID, -} from 'graphql' +import { GraphQLInt, GraphQLBoolean, GraphQLString, GraphQLNonNull, GraphQLID } from 'graphql' import { toGlobalId } from 'graphql-relay' import { verifiedDomainConnection } from '../../../verified-domains/objects' @@ -18,7 +12,7 @@ describe('given the verified organization object', () => { const demoType = verifiedOrganizationType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has an acronym field', () => { const demoType = verifiedOrganizationType.getFields() @@ -90,9 +84,7 @@ describe('given the verified organization object', () => { const demoType = verifiedOrganizationType.getFields() expect(demoType).toHaveProperty('domains') - expect(demoType.domains.type).toMatchObject( - verifiedDomainConnection.connectionType, - ) + expect(demoType.domains.type).toMatchObject(verifiedDomainConnection.connectionType) }) }) @@ -101,9 +93,7 @@ describe('given the verified organization object', () => { it('returns the resolved value', () => { const demoType = verifiedOrganizationType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('verifiedOrganization', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('verifiedOrganization', 1)) }) }) describe('testing the acronym resolver', () => { @@ -124,9 +114,7 @@ describe('given the verified organization object', () => { it('returns the resolved value', () => { const demoType = verifiedOrganizationType.getFields() - expect(demoType.slug.resolve({ slug: 'organization-name' })).toEqual( - 'organization-name', - ) + expect(demoType.slug.resolve({ slug: 'organization-name' })).toEqual('organization-name') }) }) describe('testing the zone resolver', () => { @@ -147,18 +135,14 @@ describe('given the verified organization object', () => { it('returns the resolved value', () => { const demoType = verifiedOrganizationType.getFields() - expect(demoType.country.resolve({ country: 'Canada' })).toEqual( - 'Canada', - ) + expect(demoType.country.resolve({ country: 'Canada' })).toEqual('Canada') }) }) describe('testing the province resolver', () => { it('returns the resolved value', () => { const demoType = verifiedOrganizationType.getFields() - expect(demoType.province.resolve({ province: 'province' })).toEqual( - 'province', - ) + expect(demoType.province.resolve({ province: 'province' })).toEqual('province') }) }) describe('testing the city resolver', () => { @@ -250,9 +234,7 @@ describe('given the verified organization object', () => { { first: 1 }, { loaders: { - loadVerifiedDomainConnectionsByOrgId: jest - .fn() - .mockReturnValue(expectedResult), + loadVerifiedDomainConnectionsByOrgId: jest.fn().mockReturnValue(expectedResult), }, }, ), diff --git a/api-js/src/verified-organizations/objects/index.js b/api/src/verified-organizations/objects/index.js similarity index 100% rename from api-js/src/verified-organizations/objects/index.js rename to api/src/verified-organizations/objects/index.js diff --git a/api-js/src/verified-organizations/objects/verified-organization-connection.js b/api/src/verified-organizations/objects/verified-organization-connection.js similarity index 100% rename from api-js/src/verified-organizations/objects/verified-organization-connection.js rename to api/src/verified-organizations/objects/verified-organization-connection.js diff --git a/api-js/src/verified-organizations/objects/verified-organization.js b/api/src/verified-organizations/objects/verified-organization.js similarity index 100% rename from api-js/src/verified-organizations/objects/verified-organization.js rename to api/src/verified-organizations/objects/verified-organization.js diff --git a/api-js/src/verified-organizations/queries/__tests__/find-verified-organization-by-slug.test.js b/api/src/verified-organizations/queries/__tests__/find-verified-organization-by-slug.test.js similarity index 83% rename from api-js/src/verified-organizations/queries/__tests__/find-verified-organization-by-slug.test.js rename to api/src/verified-organizations/queries/__tests__/find-verified-organization-by-slug.test.js index 771951010d..c994d08ea4 100644 --- a/api-js/src/verified-organizations/queries/__tests__/find-verified-organization-by-slug.test.js +++ b/api/src/verified-organizations/queries/__tests__/find-verified-organization-by-slug.test.js @@ -1,16 +1,17 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema, GraphQLError } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadVerifiedDomainConnectionsByOrgId } from '../../../verified-domains/loaders' import { loadVerifiedOrgBySlug } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -39,11 +40,15 @@ describe('given findOrganizationBySlugQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -101,9 +106,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('authorized user queries organization by slug', () => { it('returns organization', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizationBySlug( orgSlug: "treasury-board-secretariat" @@ -112,8 +117,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -125,15 +130,14 @@ describe('given findOrganizationBySlugQuery', () => { language: 'en', i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -163,9 +167,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('authorized user queries organization by slug', () => { it('returns organization', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizationBySlug( orgSlug: "secretariat-conseil-tresor" @@ -174,8 +178,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -187,15 +191,14 @@ describe('given findOrganizationBySlugQuery', () => { language: 'fr', i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -228,9 +231,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('organization can not be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizationBySlug( orgSlug: "not-treasury-board-secretariat" @@ -239,8 +242,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), validators: { @@ -252,13 +255,9 @@ describe('given findOrganizationBySlugQuery', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - `No verified organization with the provided slug could be found.`, - ), - ] + const error = [new GraphQLError(`No verified organization with the provided slug could be found.`)] expect(response.errors).toEqual(error) }) @@ -281,9 +280,9 @@ describe('given findOrganizationBySlugQuery', () => { }) describe('organization can not be found', () => { it('returns an appropriate error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizationBySlug( orgSlug: "ne-pas-secretariat-conseil-tresor" @@ -292,8 +291,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), validators: { @@ -305,13 +304,9 @@ describe('given findOrganizationBySlugQuery', () => { }, }, }, - ) + }) - const error = [ - new GraphQLError( - `Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.`, - ), - ] + const error = [new GraphQLError(`Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.`)] expect(response.errors).toEqual(error) }) diff --git a/api-js/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js b/api/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js similarity index 86% rename from api-js/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js rename to api/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js index d2e3e5708e..2b212ac3d8 100644 --- a/api-js/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js +++ b/api/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js @@ -1,16 +1,17 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { graphql, GraphQLSchema } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { databaseOptions } from '../../../../database-options' import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadVerifiedDomainConnectionsByOrgId } from '../../../verified-domains/loaders' import { loadVerifiedOrgConnections } from '../../loaders' +import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -39,11 +40,15 @@ describe('given findVerifiedOrganizations', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - type: 'database', - name: dbNameFromFile(__filename), - url, - rootPassword: rootPass, - options: databaseOptions({ rootPass }), + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, })) }) beforeEach(async () => { @@ -121,9 +126,9 @@ describe('given findVerifiedOrganizations', () => { }) describe('user queries for verified organizations', () => { it('returns organizations', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizations(first: 5) { edges { @@ -142,8 +147,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -152,15 +157,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -195,9 +199,9 @@ describe('given findVerifiedOrganizations', () => { describe('no organizations are found', () => { it('returns empty connection fields', async () => { await truncate() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizations(first: 5) { edges { @@ -216,8 +220,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -226,15 +230,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -271,9 +274,9 @@ describe('given findVerifiedOrganizations', () => { }) describe('user queries for verified organizations', () => { it('returns organizations', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizations(first: 5) { edges { @@ -292,8 +295,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -302,15 +305,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -345,9 +347,9 @@ describe('given findVerifiedOrganizations', () => { describe('no organizations are found', () => { it('returns empty connection fields', async () => { await truncate() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` query { findVerifiedOrganizations(first: 5) { edges { @@ -366,8 +368,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -376,15 +378,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { diff --git a/api/src/verified-organizations/queries/find-verified-organization-by-slug.js b/api/src/verified-organizations/queries/find-verified-organization-by-slug.js new file mode 100644 index 0000000000..e45d1ab8a9 --- /dev/null +++ b/api/src/verified-organizations/queries/find-verified-organization-by-slug.js @@ -0,0 +1,29 @@ +import { GraphQLNonNull } from 'graphql' +import { t } from '@lingui/macro' +import { Slug } from '../../scalars' +import { verifiedOrganizationType } from '../objects' + +export const findVerifiedOrganizationBySlug = { + type: verifiedOrganizationType, + description: 'Select all information on a selected verified organization.', + args: { + orgSlug: { + type: new GraphQLNonNull(Slug), + description: 'The slugified organization name you want to retrieve data for.', + }, + }, + resolve: async (_, args, { i18n, loaders: { loadVerifiedOrgBySlug }, validators: { cleanseInput } }) => { + // Cleanse input + const orgSlug = cleanseInput(args.orgSlug) + + // Retrieve organization by slug + const org = await loadVerifiedOrgBySlug.load(orgSlug) + + if (typeof org === 'undefined') { + console.warn(`User could not retrieve verified organization.`) + throw new Error(i18n._(t`No verified organization with the provided slug could be found.`)) + } + + return org + }, +} diff --git a/api-js/src/verified-organizations/queries/find-verified-organizations.js b/api/src/verified-organizations/queries/find-verified-organizations.js similarity index 100% rename from api-js/src/verified-organizations/queries/find-verified-organizations.js rename to api/src/verified-organizations/queries/find-verified-organizations.js diff --git a/api-js/src/verified-organizations/queries/index.js b/api/src/verified-organizations/queries/index.js similarity index 100% rename from api-js/src/verified-organizations/queries/index.js rename to api/src/verified-organizations/queries/index.js diff --git a/api/src/web-scan/data-source.js b/api/src/web-scan/data-source.js new file mode 100644 index 0000000000..a7abd16a94 --- /dev/null +++ b/api/src/web-scan/data-source.js @@ -0,0 +1,8 @@ +import { loadWebConnectionsByDomainId, loadWebScansByWebId } from './loaders' + +export class WebScanDataSource { + constructor({ query, userKey, cleanseInput, i18n }) { + this.getConnectionsByDomainId = loadWebConnectionsByDomainId({ query, userKey, cleanseInput, i18n }) + this.getScansByWebId = loadWebScansByWebId({ query, userKey, cleanseInput, i18n }) + } +} diff --git a/api/src/web-scan/index.js b/api/src/web-scan/index.js new file mode 100644 index 0000000000..d1175cffd4 --- /dev/null +++ b/api/src/web-scan/index.js @@ -0,0 +1,3 @@ +export * from './loaders' +export * from './objects' +export * from './data-source' diff --git a/api/src/web-scan/inputs/index.js b/api/src/web-scan/inputs/index.js new file mode 100644 index 0000000000..ae86edc1a7 --- /dev/null +++ b/api/src/web-scan/inputs/index.js @@ -0,0 +1 @@ +export * from './web-order' diff --git a/api/src/web-scan/inputs/web-order.js b/api/src/web-scan/inputs/web-order.js new file mode 100644 index 0000000000..4c9bfa1282 --- /dev/null +++ b/api/src/web-scan/inputs/web-order.js @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' + +import { OrderDirection, WebOrderField } from '../../enums' + +export const webOrder = new GraphQLInputObjectType({ + name: 'WebOrder', + description: 'Ordering options for web connections.', + fields: () => ({ + field: { + type: new GraphQLNonNull(WebOrderField), + description: 'The field to order web scans by.', + }, + direction: { + type: new GraphQLNonNull(OrderDirection), + description: 'The ordering direction.', + }, + }), +}) diff --git a/api/src/web-scan/loaders/index.js b/api/src/web-scan/loaders/index.js new file mode 100644 index 0000000000..e8e9e78b37 --- /dev/null +++ b/api/src/web-scan/loaders/index.js @@ -0,0 +1,2 @@ +export * from './load-web-connections-by-domain-id' +export * from './load-web-scans-by-web-id' diff --git a/api/src/web-scan/loaders/load-web-connections-by-domain-id.js b/api/src/web-scan/loaders/load-web-connections-by-domain-id.js new file mode 100644 index 0000000000..28a278b642 --- /dev/null +++ b/api/src/web-scan/loaders/load-web-connections-by-domain-id.js @@ -0,0 +1,246 @@ +import { aql } from 'arangojs' +import { t } from '@lingui/macro' + +export const loadWebConnectionsByDomainId = + ({ query, userKey, cleanseInput, i18n }) => + async ({ limit, domainId, startDate, endDate, after, before, offset, orderBy, excludePending = true }) => { + if (limit === undefined) { + console.warn(`User: ${userKey} did not set \`limit\` argument for: loadWebConnectionsByDomainId.`) + throw new Error(i18n._(t`You must provide a \`limit\` value to properly paginate the \`web\` connection.`)) + } + + if (limit <= 0 || limit > 100) { + console.warn(`User: ${userKey} set \`limit\` argument outside accepted range: loadWebConnectionsByDomainId.`) + throw new Error( + i18n._( + t`You must provide a \`limit\` value in the range of 1-100 to properly paginate the \`web\` connection.`, + ), + ) + } + + const paginationMethodCount = [before, after, offset].reduce( + (paginationMethod, currentValue) => currentValue + (paginationMethod === undefined), + 0, + ) + + if (paginationMethodCount > 1) { + console.warn(`User: ${userKey} set multiple pagination methods for: loadWebConnectionsByDomainId.`) + throw new Error( + i18n._( + t`You must provide at most one pagination method (\`before\`, \`after\`, \`offset\`) value to properly paginate the \`web\` connection.`, + ), + ) + } + + before = cleanseInput(before) + after = cleanseInput(after) + + const usingRelayExplicitly = !!(before || after) + + const resolveCursor = (cursor) => { + const cursorString = Buffer.from(cursor, 'base64').toString('utf8').split('|') + + return cursorString.reduce((acc, currentValue) => { + const [type, id] = currentValue.split('::') + acc.push({ type, id }) + return acc + }, []) + } + + let relayBeforeTemplate = aql`` + let relayAfterTemplate = aql`` + if (usingRelayExplicitly) { + const cursorList = resolveCursor(after || before) + + if (cursorList.length === 0 || cursorList > 2) { + // TODO: throw error + } + + if (cursorList.at(-1).type !== 'id') { + // id field should always be last property + // TODO: throw error + } + + const orderByDirectionArrow = + orderBy?.direction === 'DESC' ? aql`<` : orderBy?.direction === 'ASC' ? aql`>` : null + const reverseOrderByDirectionArrow = + orderBy?.direction === 'DESC' ? aql`>` : orderBy?.direction === 'ASC' ? aql`<` : null + + relayBeforeTemplate = aql`FILTER TO_NUMBER(web._key) < TO_NUMBER(${cursorList[0].id})` + relayAfterTemplate = aql`FILTER TO_NUMBER(web._key) > TO_NUMBER(${cursorList[0].id})` + + if (cursorList.length === 2) { + relayAfterTemplate = aql` + FILTER web.${cursorList[0].type} ${orderByDirectionArrow || aql`>`} ${cursorList[0].id} + OR (web.${cursorList[0].type} == ${cursorList[0].id} + AND TO_NUMBER(web._key) > TO_NUMBER(${cursorList[1].id})) + ` + + relayBeforeTemplate = aql` + FILTER web.${cursorList[0].type} ${reverseOrderByDirectionArrow || aql`<`} ${cursorList[0].id} + OR (web.${cursorList[0].type} == ${cursorList[0].id} + AND TO_NUMBER(web._key) < TO_NUMBER(${cursorList[1].id})) + ` + } + } + + const relayDirectionString = before ? aql`DESC` : aql`ASC` + + let sortTemplate + if (!orderBy) { + sortTemplate = aql`SORT TO_NUMBER(web._key) ${relayDirectionString}` + } else { + sortTemplate = aql`SORT web.${orderBy.field} ${orderBy.direction}, TO_NUMBER(web._key) ${relayDirectionString}` + } + + let startDateFilter = aql`` + if (typeof startDate !== 'undefined') { + startDateFilter = aql` + FILTER DATE_FORMAT(web.timestamp, '%yyyy-%mm-%dd') >= DATE_FORMAT(${startDate}, '%yyyy-%mm-%dd')` + } + + let endDateFilter = aql`` + if (typeof endDate !== 'undefined') { + endDateFilter = aql` + FILTER DATE_FORMAT(web.timestamp, '%yyyy-%mm-%dd') <= DATE_FORMAT(${endDate}, '%yyyy-%mm-%dd')` + } + + let pendingFilter = aql`` + if (excludePending) { + pendingFilter = aql` + LET pendingScans = ( + FOR webScan, webScanEdge IN 1 OUTBOUND web webToWebScans + FILTER webScan.status == "pending" + RETURN webScan + ) + FILTER LENGTH(pendingScans) == 0 + ` + } + + const removeExtraSliceTemplate = aql`SLICE(websPlusOne, 0, ${limit})` + const webQuery = aql` + WITH web, webScan, domains + LET websPlusOne = ( + FOR web, e IN 1 OUTBOUND ${domainId} domainsWeb + ${startDateFilter} + ${endDateFilter} + ${pendingFilter} + ${before ? relayBeforeTemplate : relayAfterTemplate} + ${sortTemplate} + LIMIT ${limit + 1} + RETURN MERGE({ id: web._key, _type: "web" }, web) + ) + LET hasMoreRelayPage = LENGTH(websPlusOne) == ${limit} + 1 + LET hasReversePage = ${!usingRelayExplicitly} ? false : (LENGTH( + FOR web, e IN 1 OUTBOUND ${domainId} domainsWeb + ${startDateFilter} + ${endDateFilter} + ${pendingFilter} + ${before ? relayAfterTemplate : relayBeforeTemplate} + LIMIT 1 + RETURN true + ) > 0) ? true : false + LET totalCount = COUNT( + FOR web, e IN 1 OUTBOUND ${domainId} domainsWeb + ${startDateFilter} + ${endDateFilter} + ${pendingFilter} + RETURN true + ) + LET webs = ${removeExtraSliceTemplate} + + RETURN { + "webs": webs, + "hasMoreRelayPage": hasMoreRelayPage, + "hasReversePage": hasReversePage, + "totalCount": totalCount + } + ` + + let webCursor + try { + webCursor = await query`${webQuery}` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to get cursor for web document with cursor '${ + after || before + }' for domain '${domainId}', error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load web scan(s). Please try again.`)) + } + + let webInfo + try { + webInfo = await webCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to get web scan information for ${domainId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load web scan(s). Please try again.`)) + } + + const webs = webInfo.webs + + if (webs.length === 0) { + return { + edges: [], + totalCount: webInfo.totalCount, + pageInfo: { + hasPreviousPage: !usingRelayExplicitly ? false : after ? webInfo.hasReversePage : webInfo.hasMoreRelayPage, + hasNextPage: after || !usingRelayExplicitly ? webInfo.hasMoreRelayPage : webInfo.hasReversePage, + startCursor: null, + endCursor: null, + }, + } + } + + const toCursorString = (cursorObjects) => { + const cursorStringArray = cursorObjects.reduce((acc, cursorObject) => { + if (cursorObject.type === undefined || cursorObject.id === undefined) { + // TODO: throw error + } + acc.push(`${cursorObject.type}::${cursorObject.id}`) + return acc + }, []) + const cursorString = cursorStringArray.join('|') + return Buffer.from(cursorString, 'utf8').toString('base64') + } + + const edges = webs.map((web) => { + let cursor + if (orderBy) { + cursor = toCursorString([ + { + type: orderBy.field, + id: web[orderBy.field], + }, + { + type: 'id', + id: web._key, + }, + ]) + } else { + cursor = toCursorString([ + { + type: 'id', + id: web._key, + }, + ]) + } + return { + cursor: cursor, + node: web, + } + }) + + return { + edges: edges, + totalCount: webInfo.totalCount, + pageInfo: { + hasPreviousPage: !usingRelayExplicitly ? false : after ? webInfo.hasReversePage : webInfo.hasMoreRelayPage, + hasNextPage: after || !usingRelayExplicitly ? webInfo.hasMoreRelayPage : webInfo.hasReversePage, + endCursor: edges.length > 0 ? edges.at(-1).cursor : null, + startCursor: edges.length > 0 ? edges[0].cursor : null, + }, + } + } diff --git a/api/src/web-scan/loaders/load-web-scans-by-web-id.js b/api/src/web-scan/loaders/load-web-scans-by-web-id.js new file mode 100644 index 0000000000..c4262bac8c --- /dev/null +++ b/api/src/web-scan/loaders/load-web-scans-by-web-id.js @@ -0,0 +1,39 @@ +import { aql } from 'arangojs' +import { t } from '@lingui/macro' + +export const loadWebScansByWebId = + ({ query, userKey, i18n }) => + async ({ webId }) => { + if (webId === undefined) { + console.warn(`User: ${userKey} - \`webId\` argument not set for: loadWebScansByWebId.`) + throw new Error(i18n._(t`Unable to load web scan(s). Please try again.`)) + } + + const webScanQuery = aql` + WITH webScan, web + FOR webScan, e IN 1 OUTBOUND ${webId} webToWebScans + RETURN webScan + ` + + let webScanCursor + try { + webScanCursor = await query`${webScanQuery}` + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to get cursor for web scans for web '${webId}', error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load web scan(s). Please try again.`)) + } + + let webScans + try { + webScans = await webScanCursor.all() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to get web scan information for web '${webId}', error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load web scan(s). Please try again.`)) + } + + return webScans + } diff --git a/api/src/web-scan/objects/index.js b/api/src/web-scan/objects/index.js new file mode 100644 index 0000000000..679466684e --- /dev/null +++ b/api/src/web-scan/objects/index.js @@ -0,0 +1,6 @@ +export * from './tls-result' +export * from './web-scan' +export * from './web-connection-result' +export * from './web-connection' +export * from './web-scan-result' + diff --git a/api/src/web-scan/objects/tls-result.js b/api/src/web-scan/objects/tls-result.js new file mode 100644 index 0000000000..78e1d541bc --- /dev/null +++ b/api/src/web-scan/objects/tls-result.js @@ -0,0 +1,316 @@ +import { GraphQLBoolean, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' + +import { guidanceTagType } from '../../guidance-tag' + +export const tlsResultType = new GraphQLObjectType({ + name: 'TLSResult', + fields: () => ({ + ipAddress: { + type: GraphQLString, + description: `The IP address of the domain scanned.`, + resolve: async ({ ipAddress }) => ipAddress, + }, + serverLocation: { + type: serverLocationType, + description: `Information regarding the server which was scanned.`, + resolve: async ({ serverLocation }) => serverLocation, + }, + certificateChainInfo: { + type: certificateChainInfoType, + description: `Information for the TLS certificate retrieved from the scanned server.`, + resolve: async ({ certificateChainInfo }) => certificateChainInfo, + }, + supportsEcdhKeyExchange: { + type: GraphQLBoolean, + description: `Whether or not the scanned server supports ECDH key exchange.`, + resolve: async ({ supportsEcdhKeyExchange }) => supportsEcdhKeyExchange, + }, + heartbleedVulnerable: { + type: GraphQLBoolean, + description: `Whether or not the scanned server is vulnerable to heartbleed.`, + resolve: async ({ heartbleedVulnerable }) => heartbleedVulnerable, + }, + robotVulnerable: { + type: GraphQLString, + description: `Whether or not the scanned server is vulnerable to heartbleed.`, + resolve: async ({ robotVulnerable }) => robotVulnerable, + }, + ccsInjectionVulnerable: { + type: GraphQLBoolean, + description: `Whether or not the scanned server is vulnerable to CCS injection.`, + resolve: async ({ ccsInjectionVulnerable }) => ccsInjectionVulnerable, + }, + acceptedCipherSuites: { + type: acceptedCipherSuitesType, + description: `An object containing the various TLS protocols and which suites are enabled for each protocol.`, + resolve: async ({ acceptedCipherSuites }) => acceptedCipherSuites, + }, + acceptedEllipticCurves: { + type: new GraphQLList(ellipticCurveType), + description: `List of the scanned servers accepted elliptic curves and their strength.`, + resolve: async ({ acceptedEllipticCurves }) => acceptedEllipticCurves, + }, + positiveTags: { + type: new GraphQLList(guidanceTagType), + description: `List of positive tags for the scanned server from this scan.`, + resolve: async ({ positiveTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: positiveTags }) + }, + }, + neutralTags: { + type: new GraphQLList(guidanceTagType), + description: `List of neutral tags for the scanned server from this scan.`, + resolve: async ({ neutralTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: neutralTags }) + }, + }, + negativeTags: { + type: new GraphQLList(guidanceTagType), + description: `List of negative tags for the scanned server from this scan.`, + resolve: async ({ negativeTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: negativeTags }) + }, + }, + certificateStatus: { + type: GraphQLString, + description: `The compliance status of the certificate bundle for the scanned server from this scan.`, + resolve: async ({ certificateStatus }) => certificateStatus, + }, + sslStatus: { + type: GraphQLString, + description: `The compliance status for TLS for the scanned server from this scan.`, + resolve: async ({ sslStatus }) => sslStatus, + }, + protocolStatus: { + type: GraphQLString, + description: `The compliance status for TLS protocol for the scanned server from this scan.`, + resolve: async ({ protocolStatus }) => protocolStatus, + }, + cipherStatus: { + type: GraphQLString, + description: `The compliance status for cipher suites for the scanned server from this scan.`, + resolve: async ({ cipherStatus }) => cipherStatus, + }, + curveStatus: { + type: GraphQLString, + description: `The compliance status for ECDH curves for the scanned server from this scan.`, + resolve: async ({ curveStatus }) => curveStatus, + }, + }), + description: `Results of TLS scans on the given domain.`, +}) + +export const serverLocationType = new GraphQLObjectType({ + name: 'ServerLocation', + fields: () => ({ + hostname: { + type: GraphQLString, + description: `Hostname which was scanned.`, + }, + ipAddress: { + type: GraphQLString, + description: `IP address used for scan.`, + }, + }), +}) + +export const trustStoreType = new GraphQLObjectType({ + name: `TrustStore`, + description: `Trust store used to validate TLS certificate.`, + fields: () => ({ + name: { + type: GraphQLString, + description: `Name of trust store used to validate certificate.`, + }, + version: { + type: GraphQLString, + description: `Version of trust store used to validate certificate.`, + }, + }), +}) + +export const pathValidationResultsType = new GraphQLObjectType({ + name: `PathValidationResults`, + description: `Validation results from each trust store.`, + fields: () => ({ + opensslErrorString: { + type: GraphQLString, + description: `Error string which occurred when attempting to validate certificate if error exists, else null.`, + }, + wasValidationSuccessful: { + type: GraphQLBoolean, + description: `Whether or not the certificate was successfully validated.`, + }, + trustStore: { + type: trustStoreType, + description: `Trust store used to validate TLS certificate.`, + }, + }), +}) + +export const certificateType = new GraphQLObjectType({ + name: `Certificate`, + description: `Certificate from the scanned server.`, + fields: () => ({ + notValidBefore: { + type: GraphQLString, + description: `The date which the certificate becomes initially becomes valid.`, + }, + notValidAfter: { + type: GraphQLString, + description: `The date which the certificate becomes invalid.`, + }, + issuer: { + type: GraphQLString, + description: `The entity which signed the certificate.`, + }, + subject: { + type: GraphQLString, + description: `The entity for which the certificate was created for.`, + }, + expiredCert: { + type: GraphQLBoolean, + description: `Whether or not the certificate is expired.`, + }, + selfSignedCert: { + type: GraphQLBoolean, + description: `Whether or not the certificate is self-signed.`, + }, + certRevoked: { + type: GraphQLBoolean, + description: `Whether or not the certificate has been revoked.`, + }, + certRevokedStatus: { + type: GraphQLString, + description: `The status of the certificate revocation check.`, + }, + commonNames: { + type: new GraphQLList(GraphQLString), + description: `The list of common names for the given certificate.`, + }, + serialNumber: { + type: GraphQLString, + description: `The serial number for the given certificate.`, + }, + signatureHashAlgorithm: { + type: GraphQLString, + description: `The hashing algorithm used to validate this certificate.`, + }, + sanList: { + type: new GraphQLList(GraphQLString), + description: `The list of all alternative (domain)names which can use this certificate.`, + }, + }), +}) + +export const certificateChainInfoType = new GraphQLObjectType({ + name: `CertificateChainInfo`, + description: ``, + fields: () => ({ + pathValidationResults: { + type: new GraphQLList(pathValidationResultsType), + description: `Validation results from each trust store.`, + }, + badHostname: { + type: GraphQLBoolean, + description: `True if domain is not listed on the given TLS certificate.`, + }, + mustHaveStaple: { + type: GraphQLBoolean, + description: `Whether or not the TLS certificate includes the OCSP Must-Staple extension.`, + }, + leafCertificateIsEv: { + type: GraphQLBoolean, + description: `Whether or not the leaf (server) certificate is an Extended Validation (EV) certificate.`, + }, + receivedChainContainsAnchorCertificate: { + type: GraphQLBoolean, + description: `Whether or not the certificate bundle includes the anchor (root) certificate.`, + }, + receivedChainHasValidOrder: { + type: GraphQLBoolean, + description: `Whether or not the certificates in the certificate bundles are in the correct order.`, + }, + verifiedChainHasSha1Signature: { + type: GraphQLBoolean, + description: `Whether or not any certificates in the certificate bundle were signed using the SHA1 algorithm.`, + }, + verifiedChainHasLegacySymantecAnchor: { + type: GraphQLBoolean, + description: `Whether or not the certificate chain includes a distrusted Symantec certificate.`, + }, + certificateChain: { + type: new GraphQLList(certificateType), + description: `The certificate chain which was used to create the TLS connection.`, + }, + passedValidation: { + type: GraphQLBoolean, + description: `Whether or not the certificate chain passed validation.`, + }, + hasEntrustCertificate: { + type: GraphQLBoolean, + description: `Whether or not the certificate chain contains an Entrust certificate.`, + }, + }), +}) + +export const acceptedCipherSuitesType = new GraphQLObjectType({ + name: `AcceptedCipherSuites`, + description: `List of accepted cipher suites separated by TLS version.`, + fields: () => ({ + ssl2_0CipherSuites: { + type: new GraphQLList(cipherSuiteType), + description: `Accepted cipher suites for SSL2.`, + }, + ssl3_0CipherSuites: { + type: new GraphQLList(cipherSuiteType), + description: `Accepted cipher suites for SSL3.`, + }, + tls1_0CipherSuites: { + type: new GraphQLList(cipherSuiteType), + description: `Accepted cipher suites for TLS1.0.`, + }, + tls1_1CipherSuites: { + type: new GraphQLList(cipherSuiteType), + description: `Accepted cipher suites for TLS1.1.`, + }, + tls1_2CipherSuites: { + type: new GraphQLList(cipherSuiteType), + description: `Accepted cipher suites for TLS1.2.`, + }, + tls1_3CipherSuites: { + type: new GraphQLList(cipherSuiteType), + description: `Accepted cipher suites for TLS1.3.`, + }, + }), +}) + +export const cipherSuiteType = new GraphQLObjectType({ + name: `CipherSuite`, + description: `Cipher suite information.`, + fields: () => ({ + name: { + type: GraphQLString, + description: `The name of the cipher suite`, + }, + strength: { + type: GraphQLString, + description: `The strength of the cipher suite.`, + }, + }), +}) + +export const ellipticCurveType = new GraphQLObjectType({ + name: `EllipticCurve`, + description: `Elliptic curve information.`, + fields: () => ({ + name: { + type: GraphQLString, + description: `The name of the elliptic curve.`, + }, + strength: { + type: GraphQLString, + description: `The strength of the elliptic curve.`, + }, + }), +}) diff --git a/api/src/web-scan/objects/web-connection-result.js b/api/src/web-scan/objects/web-connection-result.js new file mode 100644 index 0000000000..d043e081dd --- /dev/null +++ b/api/src/web-scan/objects/web-connection-result.js @@ -0,0 +1,239 @@ +import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { GraphQLJSONObject } from 'graphql-scalars' +import { guidanceTagType } from '../../guidance-tag' + +export const webConnectionResultType = new GraphQLObjectType({ + name: 'WebConnectionResult', + fields: () => ({ + hstsStatus: { + type: GraphQLString, + description: `The compliance status for HSTS for the scanned server from this scan.`, + resolve: async ({ hstsStatus }) => hstsStatus, + }, + httpsStatus: { + type: GraphQLString, + description: `The compliance status for HTTPS for the scanned server from this scan.`, + resolve: async ({ httpsStatus }) => httpsStatus, + }, + httpLive: { + type: GraphQLBoolean, + description: `Whether or not the server is serving data over HTTP.`, + resolve: async ({ httpLive }) => httpLive, + }, + httpsLive: { + type: GraphQLBoolean, + description: `Whether or not the server is serving data over HTTPS`, + resolve: async ({ httpsLive }) => httpsLive, + }, + httpImmediatelyUpgrades: { + type: GraphQLBoolean, + description: `Whether or not HTTP connection was immediately upgraded (redirected) to HTTPS.`, + resolve: async ({ httpImmediatelyUpgrades }) => httpImmediatelyUpgrades, + }, + httpEventuallyUpgrades: { + type: GraphQLBoolean, + description: `Whether or not HTTP connection was eventually upgraded to HTTPS.`, + resolve: async ({ httpEventuallyUpgrades }) => httpEventuallyUpgrades, + }, + httpsImmediatelyDowngrades: { + type: GraphQLBoolean, + description: `Whether or not HTTPS connection is immediately downgraded to HTTP.`, + resolve: async ({ httpsImmediatelyDowngrades }) => httpsImmediatelyDowngrades, + }, + httpsEventuallyDowngrades: { + type: GraphQLBoolean, + description: `Whether or not HTTPS connection is eventually downgraded to HTTP.`, + resolve: async ({ httpsEventuallyDowngrades }) => httpsEventuallyDowngrades, + }, + hstsParsed: { + type: hstsParsedType, + description: `The parsed values for the HSTS header.`, + resolve: async ({ hstsParsed }) => hstsParsed, + }, + ipAddress: { + type: GraphQLString, + description: `The IP address for the scanned server.`, + resolve: async ({ ipAddress }) => ipAddress, + }, + httpChainResult: { + type: connectionChainResultType, + description: `The chain of connections created when visiting the domain using HTTP.`, + resolve: async ({ httpChainResult }) => httpChainResult, + }, + httpsChainResult: { + type: connectionChainResultType, + description: `The chain of connections created when visiting the domain using HTTPS.`, + resolve: async ({ httpsChainResult }) => httpsChainResult, + }, + positiveTags: { + type: new GraphQLList(guidanceTagType), + description: `List of positive tags for the scanned server from this scan.`, + resolve: async ({ positiveTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: positiveTags }) + }, + }, + neutralTags: { + type: new GraphQLList(guidanceTagType), + description: `List of neutral tags for the scanned server from this scan.`, + resolve: async ({ neutralTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: neutralTags }) + }, + }, + negativeTags: { + type: new GraphQLList(guidanceTagType), + description: `List of negative tags for the scanned server from this scan.`, + resolve: async ({ negativeTags }, _, { dataSources: { guidanceTag } }) => { + return await guidanceTag.byTagId({ tags: negativeTags }) + }, + }, + }), + description: `Results of HTTP connection scan on the given domain.`, +}) + +export const hstsParsedType = new GraphQLObjectType({ + name: `HSTSParsed`, + description: `The parsed values of the HSTS header.`, + fields: () => ({ + maxAge: { + type: GraphQLInt, + description: `How long to trust the HSTS header.`, + }, + includeSubdomains: { + type: GraphQLBoolean, + description: `Whether or not this HSTS policy should apply to subdomains.`, + }, + preload: { + type: GraphQLBoolean, + description: `Whether or not the HSTS header includes the 'preload' option.`, + }, + }), +}) + +export const connectionInfo = new GraphQLObjectType({ + name: `ConnectionInfo`, + description: `Detailed info for a given connection.`, + fields: () => ({ + statusCode: { + type: GraphQLInt, + description: `The HTTP response status code.`, + }, + redirectTo: { + type: GraphQLString, + description: `The redirect location from the HTTP response.`, + }, + headers: { + type: GraphQLJSONObject, + description: `The response headers from the HTTP response. The keys of the response are the header keys.`, + }, + blockedCategory: { + type: GraphQLString, + description: `The detected category for the domain if blocked by firewall.`, + }, + HSTS: { + type: GraphQLBoolean, + description: `Whether or not the response included an HSTS header.`, + }, + }), +}) + +export const connectionType = new GraphQLObjectType({ + name: `Connection`, + description: `An HTTP (or HTTPS) connection.`, + fields: () => ({ + uri: { + type: GraphQLString, + description: `The URI for the given connection.`, + }, + connection: { + type: connectionInfo, + description: `Detailed information for a given connection.`, + }, + error: { + type: GraphQLString, + description: `Any errors which occurred when attempting to create this connection.`, + }, + scheme: { + type: GraphQLString, + description: `The connection protocol used for this connection (HTTP or HTTPS).`, + }, + }), +}) + +export const connectionChainResultType = new GraphQLObjectType({ + name: `ConnectionChainResult`, + description: `Information collected while checking HTTP connections while following redirects.`, + fields: () => ({ + scheme: { + type: GraphQLString, + description: `The connection protocol used for the initial connection to the server (HTTP or HTTPS).`, + }, + domain: { + type: GraphQLString, + description: `The domain the scan was run on.`, + }, + uri: { + type: GraphQLString, + description: `The initial full connection URI.`, + }, + hasRedirectLoop: { + type: GraphQLBoolean, + description: `Whether or not a redirection loop is created (causing endless redirects).`, + }, + connections: { + type: new GraphQLList(connectionType), + description: `The connection chain created when following redirects.`, + }, + securityTxt: { + type: new GraphQLList(securityTxtResultType), + description: 'Result of fetching and parsing the security.txt file for this domain.', + }, + }), +}) + +export const securityTxtResultType = new GraphQLObjectType({ + name: 'SecurityTxtResult', + description: 'Represents the result of a security.txt file fetch and parse operation.', + fields: () => ({ + path: { + type: GraphQLString, + description: + 'The path where the security.txt file was requested (e.g., /.well-known/security.txt or /security.txt).', + }, + url: { + type: GraphQLString, + description: 'The full URL used to fetch the security.txt file.', + }, + statusCode: { + type: GraphQLInt, + description: 'The HTTP status code returned when requesting the security.txt file.', + }, + fields: { + type: GraphQLJSONObject, + description: 'Parsed fields from the security.txt file as key-value pairs.', + }, + isValid: { + type: GraphQLBoolean, + description: 'Whether the security.txt file was found and successfully parsed.', + }, + error: { + type: GraphQLString, + description: 'Any errors encountered during fetching or parsing the security.txt file.', + }, + raw: { + type: GraphQLString, + description: 'The raw contents of the security.txt file, if available.', + }, + redirected: { + type: GraphQLBoolean, + description: 'Whether the request for security.txt was redirected.', + }, + redirectLocation: { + type: GraphQLString, + description: 'The location to which the request was redirected, if any.', + }, + redirectStatusCode: { + type: GraphQLInt, + description: 'The HTTP status code returned by any redirect encountered.', + }, + }), +}) diff --git a/api/src/web-scan/objects/web-connection.js b/api/src/web-scan/objects/web-connection.js new file mode 100644 index 0000000000..f167c1f912 --- /dev/null +++ b/api/src/web-scan/objects/web-connection.js @@ -0,0 +1,16 @@ +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' + +import {webType} from "./web" + +export const webConnection = connectionDefinitions({ + name: 'Web', + nodeType: webType, + connectionFields: () => ({ + totalCount: { + type: GraphQLInt, + description: 'The total amount of web scans related to a given domain.', + resolve: ({totalCount}) => totalCount, + }, + }), +}) diff --git a/api/src/web-scan/objects/web-scan-result.js b/api/src/web-scan/objects/web-scan-result.js new file mode 100644 index 0000000000..a96ffc1753 --- /dev/null +++ b/api/src/web-scan/objects/web-scan-result.js @@ -0,0 +1,27 @@ +import { GraphQLObjectType } from 'graphql' + +import { tlsResultType } from './tls-result' +import { webConnectionResultType } from './web-connection-result' +import { GraphQLDateTime } from 'graphql-scalars' + +export const webScanResultType = new GraphQLObjectType({ + name: 'WebScanResult', + fields: () => ({ + timestamp: { + type: GraphQLDateTime, + description: `The time when the scan was initiated.`, + resolve: ({ timestamp }) => new Date(timestamp), + }, + tlsResult: { + type: tlsResultType, + description: `The result for the TLS scan for the scanned server.`, + resolve: async ({ tlsResult }) => tlsResult, + }, + connectionResults: { + type: webConnectionResultType, + description: `The result for the HTTP connection scan for the scanned server.`, + resolve: async ({ connectionResults }) => connectionResults, + }, + }), + description: `Results of TLS and HTTP connection scans on the given domain.`, +}) diff --git a/api/src/web-scan/objects/web-scan.js b/api/src/web-scan/objects/web-scan.js new file mode 100644 index 0000000000..8bf70f1245 --- /dev/null +++ b/api/src/web-scan/objects/web-scan.js @@ -0,0 +1,27 @@ +import { GraphQLBoolean, GraphQLObjectType, GraphQLString } from 'graphql' + +import { webScanResultType } from './web-scan-result' + +export const webScanType = new GraphQLObjectType({ + name: 'WebScan', + fields: () => ({ + ipAddress: { + type: GraphQLString, + description: `IP address for scan target.`, + }, + status: { + type: GraphQLString, + description: `The status of the scan for the given domain and IP address.`, + }, + isPrivateIp: { + type: GraphQLBoolean, + description: `Whether the IP address is a private IP address.`, + }, + results: { + type: webScanResultType, + description: `Results of TLS and HTTP connection scans on the given domain.`, + resolve: async ({ results }) => results, + }, + }), + description: `Information for the TLS and HTTP connection scans on the given domain.`, +}) diff --git a/api/src/web-scan/objects/web.js b/api/src/web-scan/objects/web.js new file mode 100644 index 0000000000..10e1ba540d --- /dev/null +++ b/api/src/web-scan/objects/web.js @@ -0,0 +1,34 @@ +import { GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { globalIdField } from 'graphql-relay' + +import { nodeInterface } from '../../node' +import { GraphQLDateTime } from 'graphql-scalars' +import { webScanType } from './web-scan' + +export const webType = new GraphQLObjectType({ + name: 'Web', + fields: () => ({ + id: globalIdField('web'), + domain: { + type: GraphQLString, + description: `The domain string the scan was ran on.`, + }, + timestamp: { + type: GraphQLDateTime, + description: `The time when the scan was initiated.`, + resolve: ({ timestamp }) => new Date(timestamp), + }, + results: { + type: new GraphQLList(webScanType), + description: `Results of the web scan at each IP address.`, + resolve: async ({ _id }, args, { dataSources: { webScan } }) => { + return await webScan.getScansByWebId({ + webId: _id, + ...args, + }) + }, + }, + }), + interfaces: [nodeInterface], + description: `Results of TLS and HTTP connection scans on the given domain.`, +}) diff --git a/api/test-docker-compose.yaml b/api/test-docker-compose.yaml new file mode 100644 index 0000000000..91fa12779b --- /dev/null +++ b/api/test-docker-compose.yaml @@ -0,0 +1,20 @@ +version: '3' + +services: + nats-test: + image: nats:2.9.11-scratch + container_name: nats-test + command: -js + restart: always + ports: + - "14222:4222" + + arangodb-test: + image: arangodb:3.12.1 + container_name: arangodb-test + environment: + - ARANGO_ROOT_PASSWORD=test + + restart: unless-stopped + ports: + - "18529:8529" diff --git a/app/README.md b/app/README.md deleted file mode 100644 index 96ea967ed9..0000000000 --- a/app/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# App manifest files - -Trackers deployment configuration is split into app and platform sections. This folder contains the app manifests: The base deployment and Service objects along with the apps use of whatever CRDs are supplied by the platform. - -Environment specific variations can be found in the subfolders of the overlays directory. -To generate these manifests you need to install [kustomize](https://kustomize.io/). - -Before applying these please check out the README.md in each overlay to understand the individual configurations. - -## Getting started - -In accordance with the [12Factor app](https://12factor.net) philosophy, all the services [draws their config from the environment](https://12factor.net/config). -To generate the config needed to run a copy of the app, we need to define some `.env` files, that will be used to create Kubernetes secrets, whose values are available to the various parts of the app. - -``` -# let the super user account be created with the default username/password -$ make credentials -# or override the default credentials by passing your own. -$ make credentials displayname=admin email=admin@example.com password=admin -Credentials written to app/creds/dev -``` - -Next we can start minikube (throwing lots of resources at it). - -``` -minikube start --cpus 8 --memory 20480 -``` -Then we load the secrets and platform config into minikube. - -``` -$ make secrets env=minikube -$ make platform env=minikube -``` - -Watch the results with `watch kubectl get pods -n istio-system`. Once Istio is running (and ready to inject it's sidecar proxies), the config for our app can be applied. - -``` -$ make app env=minikube -``` - -Depending on the speed of your system you might need to run the kustomize/apply commands more than once. - -### Seeing the result: - -The app lets you connect to both ports 80 and 443 (which is using a self signed certificate). - -```bash -$ minikube service list -``` diff --git a/app/aks/README.md b/app/aks/README.md deleted file mode 100644 index 1aff5aeb4f..0000000000 --- a/app/aks/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# AKS - -The purpose of this overlay is to bring up Tracker on AKS. The config here is just a draft. We're just getting started! - -If you need some dev credentials for this test cluster, you can generate them with `make credentials mode=dev`. - -## Bringing up a cluster on AKS - -Normally we would create the cluster with `make cluster`, but we haven't gotten an AKS equivalent worked out yet. In the absence of that, we'll just assume you already have a 6 node cluster somehow, and `kubectl` configured to talk to it. - -## Installing Tracker - -Just run the following commands. - -```sh -make secrets env=aks -# If either of these fail, just run them again -make platform env=aks -make app env=aks -``` - -If you want to watch the pod creation process, you can do it with this: - -```sh -watch kubectl get po -A -``` - -That will bring the cluster up with a self-signed certificate. To connect to it, we just need the external IP. - -```sh -kubectl get svc -n istio-system istio-ingressgateway -``` - -Connecting to both `https://` and `https:///graphql` should succeed. Reaching the frontend and API respectively. - -## Loading data - -The database isn't exposed to the outside world, so loading data requires you to forward the database ports to your local machine. - -```sh -kubectl port-forward -n db svc/arangodb 8529:8529 -``` - -With that port forwarding in place, you can now load/dump with the following commands: - -```sh -arangodump --server.database track_dmarc --output-directory track_dmarc-$(date --iso-8601) -arangorestore --create-database --server.database track_dmarc --input-directory track_dmarc-2021-05-12 -``` diff --git a/app/aks/arangodb-deployment.yaml b/app/aks/arangodb-deployment.yaml deleted file mode 100644 index f337749af9..0000000000 --- a/app/aks/arangodb-deployment.yaml +++ /dev/null @@ -1,17 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - agents: - volumeClaimTemplate: - spec: - storageClassName: slow-retain - persistentVolumeReclaimPolicy: Retain - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain - diff --git a/app/aks/certificate.yaml b/app/aks/certificate.yaml deleted file mode 100644 index 07455fde69..0000000000 --- a/app/aks/certificate.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ingress-cert - namespace: istio-system -spec: - commonName: tracker.canada.ca - dnsNames: - - tracker.canada.ca - - suivi.canada.ca - issuerRef: - kind: Issuer - name: letsencrypt-staging - privateKey: - algorithm: RSA - encoding: PKCS8 # ITSP.40.062 6.2 Signature Algorithms - size: 4096 - secretName: tracker-credential -status: {} diff --git a/app/aks/kustomization.yaml b/app/aks/kustomization.yaml deleted file mode 100644 index 2a1d6c9604..0000000000 --- a/app/aks/kustomization.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -patchesStrategicMerge: -- arangodb-deployment.yaml -- certificate.yaml -- publicgateway.yaml -replicas: -- count: 2 - name: tracker-frontend -- count: 2 - name: tracker-api -- count: 1 - name: arango-deployment-replication-operator -- count: 1 - name: arango-deployment-operator -- count: 1 - name: arango-storage-operator -components: -- ../namespaces diff --git a/app/aks/publicgateway.yaml b/app/aks/publicgateway.yaml deleted file mode 100644 index 1aa2ebb443..0000000000 --- a/app/aks/publicgateway.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: publicgateway - namespace: istio-system - labels: - istio: publicgateway -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - hosts: - - "*" - tls: - httpsRedirect: true - - port: - number: 443 - name: https - protocol: HTTPS - hosts: - - "*" - tls: - mode: SIMPLE - credentialName: tracker-credential - privateKey: sds - serverCertificate: sds - minProtocolVersion: TLSV1_2 - cipherSuites: - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 diff --git a/app/aks/storage-classes.yaml b/app/aks/storage-classes.yaml deleted file mode 100644 index 642902e863..0000000000 --- a/app/aks/storage-classes.yaml +++ /dev/null @@ -1,50 +0,0 @@ -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: fast-retain -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Retain -parameters: - storageaccounttype: Premium_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: slow-retain -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Retain -parameters: - storageaccounttype: Standard_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: fast-delete -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Delete -parameters: - storageaccounttype: Premium_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: slow-delete -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Delete -parameters: - storageaccounttype: Standard_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer ---- diff --git a/app/arangodb/db-deployment.yaml b/app/arangodb/db-deployment.yaml deleted file mode 100644 index 01f23ae416..0000000000 --- a/app/arangodb/db-deployment.yaml +++ /dev/null @@ -1,27 +0,0 @@ -kind: "ArangoDeployment" -apiVersion: database.arangodb.com/v1alpha -metadata: - name: "arangodb" - namespace: api -spec: - metadata: - annotations: - sidecar.istio.io/inject: "true" - image: arangodb/arangodb:3.7.3 - environment: Development - mode: Single - tls: - caSecretName: None - externalAccess: - type: None - bootstrap: - passwordSecretNames: - root: arango-root - metrics: - enabled: true - coordinators: - count: 1 - agents: - count: 1 - dbservers: - count: 1 diff --git a/app/arangodb/kustomization.yaml b/app/arangodb/kustomization.yaml deleted file mode 100644 index be73bed62a..0000000000 --- a/app/arangodb/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: db - -resources: -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.1.9/manifests/arango-crd.yaml -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.1.9/manifests/arango-deployment.yaml -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.1.9/manifests/arango-storage.yaml -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.1.9/manifests/arango-deployment-replication.yaml diff --git a/app/bases/README.md b/app/bases/README.md deleted file mode 100644 index 6085ff7964..0000000000 --- a/app/bases/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Base manifests - -This folder contains the base manifests that [Kustomize](https://kustomize.io/) will add to or patch using the contents of the overlay folders. - -The only real thing of note here is the `istio.yaml` file, which was generated by the following command: - -```bash -istioctl manifest generate --set values.global.jwtPolicy=first-party-jwt --set values.kiali.enabled=true --set values.tracing.enabled=true --set values.pilot.traceSampling=100 --set meshConfig.accessLogFile="/dev/stdout" > istio.yaml -``` - -At the moment the istio service opens a few more ports than we actually want to expose, so after running the above command you will need to remove the offending ports from the service definition so the ingress gateway doesn't open them. That means editing the istio.yaml and removing the following: - -```bash - apiVersion: v1 - kind: Service - metadata: - annotations: null - labels: - app: istio-ingressgateway - istio: ingressgateway - release: istio - name: istio-ingressgateway - namespace: istio-system - spec: - ports: -- - name: status-port -- port: 15021 -- targetPort: 15021 - - name: http2 - port: 80 - targetPort: 8080 - - name: https - port: 443 - targetPort: 8443 -- - name: tls -- port: 15443 -- targetPort: 15443 - selector: - app: istio-ingressgateway - istio: ingressgateway - type: LoadBalancer -``` - -There is another `istio.yaml` in the GKE overlay folder that patches this config with settings needed for our GKE environment. - diff --git a/app/bases/arangodb-deployment.yaml b/app/bases/arangodb-deployment.yaml deleted file mode 100644 index 9cc42437ce..0000000000 --- a/app/bases/arangodb-deployment.yaml +++ /dev/null @@ -1,56 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - image: arangodb/arangodb:3.7.11 - environment: Production - # environment: Development - mode: Cluster - tls: - caSecretName: None - externalAccess: - type: None - bootstrap: - passwordSecretNames: - root: arangodb - metrics: - enabled: true - agents: - count: 3 - volumeClaimTemplate: - spec: - storageClassName: slow-delete - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 8Gi - volumeMode: Filesystem - persistentVolumeReclaimPolicy: Delete - dbservers: - count: 3 - volumeClaimTemplate: - spec: - storageClassName: fast-delete - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 80Gi - volumeMode: Filesystem - persistentVolumeReclaimPolicy: Delete - coordinators: - count: 3 - single: - volumeClaimTemplate: - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi - volumeMode: Filesystem - storageClassName: standard2 - diff --git a/app/bases/arangodb-operator.yaml b/app/bases/arangodb-operator.yaml deleted file mode 100644 index 44afd6276b..0000000000 --- a/app/bases/arangodb-operator.yaml +++ /dev/null @@ -1,1222 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/instance: crd - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb-crd - helm.sh/chart: kube-arangodb-crd-1.1.9 - release: crd - name: arangobackuppolicies.backup.arangodb.com -spec: - group: backup.arangodb.com - names: - kind: ArangoBackupPolicy - listKind: ArangoBackupPolicyList - plural: arangobackuppolicies - shortNames: - - arangobackuppolicy - - arangobp - singular: arangobackuppolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Schedule - jsonPath: .spec.schedule - name: Schedule - type: string - - description: Scheduled - jsonPath: .status.scheduled - name: Scheduled - type: string - - description: Message of the ArangoBackupPolicy object - jsonPath: .status.message - name: Message - priority: 1 - type: string - name: v1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - description: Schedule - jsonPath: .spec.schedule - name: Schedule - type: string - - description: Scheduled - jsonPath: .status.scheduled - name: Scheduled - type: string - - description: Message of the ArangoBackupPolicy object - jsonPath: .status.message - name: Message - priority: 1 - type: string - name: v1alpha - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/instance: crd - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb-crd - helm.sh/chart: kube-arangodb-crd-1.1.9 - release: crd - name: arangobackups.backup.arangodb.com -spec: - group: backup.arangodb.com - names: - kind: ArangoBackup - listKind: ArangoBackupList - plural: arangobackups - shortNames: - - arangobackup - singular: arangobackup - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Policy name - jsonPath: .spec.policyName - name: Policy - type: string - - description: Deployment name - jsonPath: .spec.deployment.name - name: Deployment - type: string - - description: Backup Version - jsonPath: .status.backup.version - name: Version - type: string - - description: Backup Creation Timestamp - jsonPath: .status.backup.createdAt - name: Created - type: string - - description: Backup Size in Bytes - format: byte - jsonPath: .status.backup.sizeInBytes - name: Size - type: integer - - description: Backup Number of the DB Servers - jsonPath: .status.backup.numberOfDBServers - name: DBServers - type: integer - - description: The actual state of the ArangoBackup - jsonPath: .status.state - name: State - type: string - - description: Message of the ArangoBackup object - jsonPath: .status.message - name: Message - priority: 1 - type: string - name: v1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - description: Policy name - jsonPath: .spec.policyName - name: Policy - type: string - - description: Deployment name - jsonPath: .spec.deployment.name - name: Deployment - type: string - - description: Backup Version - jsonPath: .status.backup.version - name: Version - type: string - - description: Backup Creation Timestamp - jsonPath: .status.backup.createdAt - name: Created - type: string - - description: Backup Size in Bytes - format: byte - jsonPath: .status.backup.sizeInBytes - name: Size - type: integer - - description: Backup Number of the DB Servers - jsonPath: .status.backup.numberOfDBServers - name: DBServers - type: integer - - description: The actual state of the ArangoBackup - jsonPath: .status.state - name: State - type: string - - description: Message of the ArangoBackup object - jsonPath: .status.message - name: Message - priority: 1 - type: string - name: v1alpha - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/instance: crd - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb-crd - helm.sh/chart: kube-arangodb-crd-1.1.9 - release: crd - name: arangodeploymentreplications.replication.database.arangodb.com -spec: - group: replication.database.arangodb.com - names: - kind: ArangoDeploymentReplication - listKind: ArangoDeploymentReplicationList - plural: arangodeploymentreplications - shortNames: - - arangorepl - singular: arangodeploymentreplication - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: true - - name: v1alpha - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - - name: v2alpha1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/instance: crd - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb-crd - helm.sh/chart: kube-arangodb-crd-1.1.9 - release: crd - name: arangodeployments.database.arangodb.com -spec: - group: database.arangodb.com - names: - kind: ArangoDeployment - listKind: ArangoDeploymentList - plural: arangodeployments - shortNames: - - arangodb - - arango - singular: arangodeployment - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: true - - name: v1alpha - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - - name: v2alpha1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/instance: crd - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb-crd - helm.sh/chart: kube-arangodb-crd-1.1.9 - release: crd - name: arangomembers.database.arangodb.com -spec: - group: database.arangodb.com - names: - kind: ArangoMember - listKind: ArangoMemberList - plural: arangomembers - shortNames: - - arangomembers - singular: arangomember - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: true - subresources: - status: {} - - name: v2alpha1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arangolocalstorages.storage.arangodb.com -spec: - group: storage.arangodb.com - names: - kind: ArangoLocalStorage - listKind: ArangoLocalStorageList - plural: arangolocalstorages - shortNames: - - arangostorage - singular: arangolocalstorage - scope: Cluster - version: v1alpha ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator - namespace: db ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator - namespace: db ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator-rbac-default - namespace: db -rules: -- apiGroups: - - "" - resources: - - pods - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator-rbac-deployment - namespace: db -rules: -- apiGroups: - - database.arangodb.com - resources: - - arangodeployments - - arangodeployments/status - - arangomembers - - arangomembers/status - verbs: - - '*' -- apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - - secrets - - serviceaccounts - verbs: - - '*' -- apiGroups: - - apps - resources: - - deployments - - replicasets - verbs: - - get -- apiGroups: - - policy - resources: - - poddisruptionbudgets - verbs: - - '*' -- apiGroups: - - backup.arangodb.com - resources: - - arangobackuppolicies - - arangobackups - verbs: - - get - - list - - watch -- apiGroups: - - monitoring.coreos.com - resources: - - servicemonitors - verbs: - - get - - create - - delete - - update - - list - - watch - - patch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator-rbac-deployment-replication - namespace: db -rules: -- apiGroups: - - replication.database.arangodb.com - resources: - - arangodeploymentreplications - - arangodeploymentreplications/status - verbs: - - '*' -- apiGroups: - - database.arangodb.com - resources: - - arangodeployments - verbs: - - get -- apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - - secrets - verbs: - - '*' -- apiGroups: - - apps - resources: - - deployments - - replicasets - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator-rbac-storage - namespace: db -rules: -- apiGroups: - - "" - resources: - - pods - verbs: - - get - - update -- apiGroups: - - "" - resources: - - secrets - verbs: - - get -- apiGroups: - - apps - resources: - - daemonsets - verbs: - - '*' -- apiGroups: - - apps - resources: - - deployments - - replicasets - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator-rbac-deployment -rules: -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - namespaces - - nodes - - persistentvolumes - verbs: - - get - - list ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator-rbac-deployment-replication -rules: -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - namespaces - - nodes - verbs: - - get - - list ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator-rbac-storage -rules: -- apiGroups: - - "" - resources: - - persistentvolumes - - persistentvolumeclaims - - endpoints - - events - - services - verbs: - - '*' -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - namespaces - - nodes - verbs: - - get - - list -- apiGroups: - - storage.k8s.io - resources: - - storageclasses - verbs: - - '*' -- apiGroups: - - storage.arangodb.com - resources: - - arangolocalstorages - verbs: - - '*' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator-rbac-default - namespace: db -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: arango-deployment-operator-rbac-default -subjects: -- kind: ServiceAccount - name: default - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator-rbac-deployment - namespace: db -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: arango-deployment-operator-rbac-deployment -subjects: -- kind: ServiceAccount - name: arango-deployment-operator - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator-rbac-deployment-replication - namespace: db -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: arango-deployment-replication-operator-rbac-deployment-replication -subjects: -- kind: ServiceAccount - name: arango-deployment-replication-operator - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator-rbac-storage - namespace: db -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: arango-storage-operator-rbac-storage -subjects: -- kind: ServiceAccount - name: arango-storage-operator - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator-rbac-deployment -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: arango-deployment-operator-rbac-deployment -subjects: -- kind: ServiceAccount - name: arango-deployment-operator - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator-rbac-deployment-replication -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: arango-deployment-replication-operator-rbac-deployment-replication -subjects: -- kind: ServiceAccount - name: arango-deployment-replication-operator - namespace: db ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator-rbac-storage -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: arango-storage-operator-rbac-storage -subjects: -- kind: ServiceAccount - name: arango-storage-operator - namespace: db ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator - namespace: db -spec: - ports: - - name: server - port: 8528 - protocol: TCP - targetPort: 8528 - selector: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - release: deployment - role: leader - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator - namespace: db -spec: - ports: - - name: server - port: 8528 - protocol: TCP - targetPort: 8528 - selector: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - release: deployment-replication - role: leader - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator - namespace: db -spec: - ports: - - name: server - port: 8528 - protocol: TCP - targetPort: 8528 - selector: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - release: storage - role: leader - type: ClusterIP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - name: arango-deployment-operator - namespace: db -spec: - replicas: 2 - selector: - matchLabels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - release: deployment - strategy: - type: Recreate - template: - metadata: - labels: - app.kubernetes.io/instance: deployment - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: beta.kubernetes.io/arch - operator: In - values: - - amd64 - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: In - values: - - kube-arangodb - - key: app.kubernetes.io/instance - operator: In - values: - - deployment - topologyKey: kubernetes.io/hostname - weight: 100 - containers: - - args: - - --scope=legacy - - --operator.deployment - - --chaos.allowed=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: MY_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: RELATED_IMAGE_UBI - value: alpine:3.11 - - name: RELATED_IMAGE_METRICSEXPORTER - value: arangodb/arangodb-exporter:0.1.7 - - name: RELATED_IMAGE_DATABASE - value: arangodb/arangodb:latest - image: arangodb/kube-arangodb:1.1.9 - imagePullPolicy: Always - livenessProbe: - httpGet: - path: /health - port: 8528 - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - name: operator - ports: - - containerPort: 8528 - name: metrics - readinessProbe: - httpGet: - path: /ready - port: 8528 - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - readOnlyRootFilesystem: true - hostIPC: false - hostNetwork: false - hostPID: false - securityContext: - runAsNonRoot: true - runAsUser: 1000 - serviceAccountName: arango-deployment-operator - tolerations: - - effect: NoExecute - key: node.kubernetes.io/unreachable - operator: Exists - tolerationSeconds: 5 - - effect: NoExecute - key: node.kubernetes.io/not-ready - operator: Exists - tolerationSeconds: 5 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - name: arango-deployment-replication-operator - namespace: db -spec: - replicas: 2 - selector: - matchLabels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - release: deployment-replication - strategy: - type: Recreate - template: - metadata: - labels: - app.kubernetes.io/instance: deployment-replication - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: deployment-replication - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: beta.kubernetes.io/arch - operator: In - values: - - amd64 - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: In - values: - - kube-arangodb - - key: app.kubernetes.io/instance - operator: In - values: - - deployment-replication - topologyKey: kubernetes.io/hostname - weight: 100 - containers: - - args: - - --scope=legacy - - --operator.deployment-replication - - --chaos.allowed=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: MY_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: RELATED_IMAGE_UBI - value: alpine:3.11 - - name: RELATED_IMAGE_METRICSEXPORTER - value: arangodb/arangodb-exporter:0.1.7 - - name: RELATED_IMAGE_DATABASE - value: arangodb/arangodb:latest - image: arangodb/kube-arangodb:1.1.9 - imagePullPolicy: Always - livenessProbe: - httpGet: - path: /health - port: 8528 - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - name: operator - ports: - - containerPort: 8528 - name: metrics - readinessProbe: - httpGet: - path: /ready - port: 8528 - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - readOnlyRootFilesystem: true - hostIPC: false - hostNetwork: false - hostPID: false - securityContext: - runAsNonRoot: true - runAsUser: 1000 - serviceAccountName: arango-deployment-replication-operator - tolerations: - - effect: NoExecute - key: node.kubernetes.io/unreachable - operator: Exists - tolerationSeconds: 5 - - effect: NoExecute - key: node.kubernetes.io/not-ready - operator: Exists - tolerationSeconds: 5 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - name: arango-storage-operator - namespace: db -spec: - replicas: 2 - selector: - matchLabels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - release: storage - strategy: - type: Recreate - template: - metadata: - labels: - app.kubernetes.io/instance: storage - app.kubernetes.io/managed-by: Tiller - app.kubernetes.io/name: kube-arangodb - helm.sh/chart: kube-arangodb-1.1.9 - release: storage - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: beta.kubernetes.io/arch - operator: In - values: - - amd64 - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: In - values: - - kube-arangodb - - key: app.kubernetes.io/instance - operator: In - values: - - storage - topologyKey: kubernetes.io/hostname - weight: 100 - containers: - - args: - - --scope=legacy - - --operator.storage - - --chaos.allowed=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: MY_POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: RELATED_IMAGE_UBI - value: alpine:3.11 - - name: RELATED_IMAGE_METRICSEXPORTER - value: arangodb/arangodb-exporter:0.1.7 - - name: RELATED_IMAGE_DATABASE - value: arangodb/arangodb:latest - image: arangodb/kube-arangodb:1.1.9 - imagePullPolicy: Always - livenessProbe: - httpGet: - path: /health - port: 8528 - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - name: operator - ports: - - containerPort: 8528 - name: metrics - readinessProbe: - httpGet: - path: /ready - port: 8528 - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - readOnlyRootFilesystem: true - hostIPC: false - hostNetwork: false - hostPID: false - securityContext: - runAsNonRoot: true - runAsUser: 1000 - serviceAccountName: arango-storage-operator - tolerations: - - effect: NoExecute - key: node.kubernetes.io/unreachable - operator: Exists - tolerationSeconds: 5 - - effect: NoExecute - key: node.kubernetes.io/not-ready - operator: Exists - tolerationSeconds: 5 diff --git a/app/bases/backup-service-account.yaml b/app/bases/backup-service-account.yaml deleted file mode 100644 index 09327462f1..0000000000 --- a/app/bases/backup-service-account.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: backup-service - namespace: db diff --git a/app/bases/certificate.yaml b/app/bases/certificate.yaml deleted file mode 100644 index 0db25c1e49..0000000000 --- a/app/bases/certificate.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ingress-cert - namespace: istio-system -spec: - commonName: tracker.alpha.canada.ca - dnsNames: - - tracker.alpha.canada.ca - - suivi.alpha.canada.ca - issuerRef: - kind: Issuer - name: selfsigned - privateKey: - algorithm: RSA - encoding: PKCS8 - size: 4096 - secretName: tracker-credential -status: {} diff --git a/app/bases/issuers.yaml b/app/bases/issuers.yaml deleted file mode 100644 index 1518bde0a7..0000000000 --- a/app/bases/issuers.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - creationTimestamp: null - name: letsencrypt - namespace: istio-system -spec: - acme: - email: mike@korora.ca - preferredChain: "" - privateKeySecretRef: - name: letsencrypt-prod - server: https://acme-v02.api.letsencrypt.org/directory - solvers: - - http01: - ingress: - class: istio - selector: {} -status: {} ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - creationTimestamp: null - name: letsencrypt-staging - namespace: istio-system -spec: - acme: - email: mike@korora.ca - preferredChain: "" - privateKeySecretRef: - name: letsencrypt-staging - server: https://acme-staging-v02.api.letsencrypt.org/directory - solvers: - - http01: - ingress: - class: istio - selector: {} -status: {} ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - creationTimestamp: null - name: selfsigned - namespace: istio-system -spec: - selfSigned: {} -status: {} diff --git a/app/bases/knative/config/autoscan.yaml b/app/bases/knative/config/autoscan.yaml deleted file mode 100755 index a50024efb5..0000000000 --- a/app/bases/knative/config/autoscan.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: autoscan - namespace: scanners -spec: - schedule: "0 */12 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - metadata: - labels: - app: scanners - role: autoscan - spec: - containers: - - name: autoscan - image: gcr.io/track-compliance/services/autoscan:master-b0e6547-1625576840 # {"$imagepolicy": "flux-system:autoscan"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - restartPolicy: OnFailure diff --git a/app/bases/knative/config/config.yaml b/app/bases/knative/config/config.yaml deleted file mode 100755 index 1191a9831d..0000000000 --- a/app/bases/knative/config/config.yaml +++ /dev/null @@ -1,196 +0,0 @@ -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: https-scanner - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/target: "4" - autoscaling.knative.dev/maxScale: "20" - spec: - timeoutSeconds: 600 - containerConcurrency: 8 - containers: - - name: https-scanner - image: gcr.io/track-compliance/services/scanners/https:master-f7096fd-1629118942 # {"$imagepolicy": "flux-system:https-scanner"} - env: - - name: SCAN_TIMEOUT - value: "20" ---- -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: result-processor - namespace: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/target: "4" - autoscaling.knative.dev/maxScale: "20" - labels: - app: scanners - role: result-processor - spec: - timeoutSeconds: 600 - containerConcurrency: 8 - containers: - - name: result-processor - image: gcr.io/track-compliance/services/results:master-8d3a6a0-1628271816 # {"$imagepolicy": "flux-system:result-processor"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - - name: REDIS_HOST - valueFrom: - secretKeyRef: - name: scanners - key: REDIS_HOST - - name: REDIS_PORT - valueFrom: - secretKeyRef: - name: scanners - key: REDIS_PORT - - name: REPO_NAME - value: "ITSP.40.062" - - name: REPO_OWNER - value: "CybercentreCanada" - - name: GUIDANCE_DIR - value: "transport-layer-security" - - name: GITHUB_TOKEN - valueFrom: - secretKeyRef: - name: scanners - key: GITHUB_TOKEN ---- -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: ssl-scanner - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/target: "4" - autoscaling.knative.dev/maxScale: "20" - spec: - timeoutSeconds: 600 - containerConcurrency: 8 - containers: - - name: ssl-scanner - image: gcr.io/track-compliance/services/scanners/ssl:master-f7096fd-1629118929 # {"$imagepolicy": "flux-system:ssl-scanner"} - env: - - name: SCAN_TIMEOUT - value: "20" ---- -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: dns-scanner - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/target: "4" - autoscaling.knative.dev/maxScale: "20" - spec: - timeoutSeconds: 600 - containerConcurrency: 8 - containers: - - name: dns-scanner - image: gcr.io/track-compliance/services/scanners/dns:master-f7096fd-1629118913 # {"$imagepolicy": "flux-system:dns-scanner"} - env: - - name: SCAN_TIMEOUT - value: "20" ---- -# K8s Service Account that runs `src`'s container. - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: service-account - namespace: scanners ---- -# The permissions that `src` needs. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: null - name: event-watcher - namespace: scanners -rules: -- apiGroups: - - "" - resources: - - events - verbs: - - get - - list - - watch ---- -# Give `src`'s service account the necessary permissions. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - creationTimestamp: null - name: k8s-ra-event-watcher - namespace: scanners -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: event-watcher -subjects: -- kind: ServiceAccount - name: service-account - namespace: scanners diff --git a/app/bases/knative/config/dmarc-report.yaml b/app/bases/knative/config/dmarc-report.yaml deleted file mode 100644 index 8d09bef8ff..0000000000 --- a/app/bases/knative/config/dmarc-report.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: dmarc-report - namespace: scanners -spec: - schedule: "30 10 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - name: dmarc-report - image: gcr.io/track-compliance/dmarc-report:master-dfaa558-1631794640 # {"$imagepolicy": "flux-system:dmarc-report"} - env: - - name: DB_NAME - valueFrom: - secretKeyRef: - name: dmarc - key: DB_NAME - - name: DB_PASS - valueFrom: - secretKeyRef: - name: dmarc - key: DB_PASS - - name: DB_URL - valueFrom: - secretKeyRef: - name: dmarc - key: DB_URL - - name: GITHUB_BRANCH - valueFrom: - secretKeyRef: - name: dmarc - key: GITHUB_BRANCH - - name: GITHUB_FILE - valueFrom: - secretKeyRef: - name: dmarc - key: GITHUB_FILE - - name: GITHUB_OWNER - valueFrom: - secretKeyRef: - name: dmarc - key: GITHUB_OWNER - - name: GITHUB_REPO - valueFrom: - secretKeyRef: - name: dmarc - key: GITHUB_REPO - - name: GITHUB_TOKEN - valueFrom: - secretKeyRef: - name: dmarc - key: GITHUB_TOKEN - - name: GITHUB_URL - valueFrom: - secretKeyRef: - name: dmarc - key: GITHUB_URL - - name: AZURE_CONN_STRING - valueFrom: - secretKeyRef: - name: dmarc - key: AZURE_CONN_STRING - - name: DATABASE - valueFrom: - secretKeyRef: - name: dmarc - key: DATABASE - - name: SUMMARIES_CONTAINER - valueFrom: - secretKeyRef: - name: dmarc - key: SUMMARIES_CONTAINER - restartPolicy: OnFailure diff --git a/app/bases/knative/config/guidance.yaml b/app/bases/knative/config/guidance.yaml deleted file mode 100755 index ccc036437a..0000000000 --- a/app/bases/knative/config/guidance.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: guidance - namespace: scanners -spec: - schedule: "59 22 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - name: guidance - image: gcr.io/track-compliance/services/guidance:master-410a212-1628257658 # {"$imagepolicy": "flux-system:guidance"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - - name: REPO_NAME - value: "tracker" - - name: REPO_OWNER - value: "canada-ca" - - name: GUIDANCE_DIR - value: "guidance" - - name: GITHUB_TOKEN - valueFrom: - secretKeyRef: - name: scanners - key: GITHUB_TOKEN - restartPolicy: OnFailure diff --git a/app/bases/knative/config/queues.yaml b/app/bases/knative/config/queues.yaml deleted file mode 100755 index ffc5b85673..0000000000 --- a/app/bases/knative/config/queues.yaml +++ /dev/null @@ -1,117 +0,0 @@ -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: scan-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "1" - spec: - timeoutSeconds: 500 - containers: - - name: scan-queue - image: gcr.io/track-compliance/services/queues/scan:master-1a887f2-1630449001 # {"$imagepolicy": "flux-system:scan-queue"} ---- -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: result-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "1" - spec: - timeoutSeconds: 500 - containers: - - name: result-queue - image: gcr.io/track-compliance/services/queues/result:master-aae5946-1627998666 # {"$imagepolicy": "flux-system:result-queue"} ---- -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: ots-scan-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "1" - spec: - timeoutSeconds: 500 - containers: - - name: ots-scan-queue - image: gcr.io/track-compliance/services/queues/ots-scan:master-2cf17ff-1628180931 # {"$imagepolicy": "flux-system:ots-scan-queue"} - env: - - name: PUBSUB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: REDIS_HOST - - name: PUBSUB_PORT - valueFrom: - secretKeyRef: - name: scanners - key: REDIS_PORT ---- -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: ots-result-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "1" - spec: - timeoutSeconds: 500 - containers: - - name: ots-result-queue - image: gcr.io/track-compliance/services/queues/ots-result:master-2cf17ff-1628180933 # {"$imagepolicy": "flux-system:ots-result-queue"} - env: - - name: PUBSUB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: REDIS_HOST - - name: PUBSUB_PORT - valueFrom: - secretKeyRef: - name: scanners - key: REDIS_PORT diff --git a/app/bases/knative/config/redis.yaml b/app/bases/knative/config/redis.yaml deleted file mode 100644 index 8d3890edbc..0000000000 --- a/app/bases/knative/config/redis.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: redis - namespace: scanners - labels: - app: scanners -spec: - containers: - - name: web - image: redis:6.2-rc3 - ports: - - containerPort: 6379 - ---- - -kind: Service -apiVersion: v1 -metadata: - name: redis-service - namespace: scanners -spec: - type: ClusterIP - selector: - app: scanners - ports: - - protocol: TCP - port: 6379 - targetPort: 6379 diff --git a/app/bases/knative/config/summaries.yaml b/app/bases/knative/config/summaries.yaml deleted file mode 100644 index a5fa798b85..0000000000 --- a/app/bases/knative/config/summaries.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: summaries - namespace: scanners -spec: - schedule: "30 10 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - name: summaries - image: gcr.io/track-compliance/services/summaries:master-8d3a6a0-1628271814 # {"$imagepolicy": "flux-system:summaries"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - restartPolicy: OnFailure diff --git a/app/bases/kustomization.yaml b/app/bases/kustomization.yaml deleted file mode 100644 index 0ea99a4495..0000000000 --- a/app/bases/kustomization.yaml +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -images: -- name: gcr.io/track-compliance/db-migration - newTag: master-0000000 -- name: gcr.io/track-compliance/dns - newTag: master-0000000 -- name: gcr.io/track-compliance/https - newTag: master-0000000 -- name: gcr.io/track-compliance/result-queue - newTag: master-0000000 -- name: gcr.io/track-compliance/results - newTag: master-0000000 -- name: gcr.io/track-compliance/scan-queue - newTag: master-0000000 -- name: gcr.io/track-compliance/ssl - newTag: master-0000000 -resources: -- knative/config/queues.yaml -- knative/config/config.yaml -- knative/config/autoscan.yaml -- knative/config/guidance.yaml -- knative/config/summaries.yaml -- knative/config/dmarc-report.yaml -- knative/config/redis.yaml -- tracker-api-deployment.yaml -- tracker-api-service.yaml -- tracker-api-virtual-service.yaml -- tracker-frontend-deployment.yaml -- tracker-frontend-service.yaml -- tracker-frontend-virtual-service.yaml -- publicgateway.yaml -- certificate.yaml -- arangodb-operator.yaml -- arangodb-deployment.yaml -- issuers.yaml -- backup-service-account.yaml diff --git a/app/bases/publicgateway.yaml b/app/bases/publicgateway.yaml deleted file mode 100644 index 05936d362c..0000000000 --- a/app/bases/publicgateway.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: publicgateway - namespace: istio-system - labels: - istio: publicgateway -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - hosts: - - "*" - - port: - number: 443 - name: https - protocol: HTTPS - hosts: - - "*" - tls: - mode: SIMPLE - credentialName: tracker-credential - privateKey: sds - serverCertificate: sds - minProtocolVersion: TLSV1_2 - cipherSuites: - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml deleted file mode 100644 index 537bf2395f..0000000000 --- a/app/bases/tracker-api-deployment.yaml +++ /dev/null @@ -1,249 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app: tracker-api - name: tracker-api - namespace: api -spec: - replicas: 1 - selector: - matchLabels: - app: tracker-api - strategy: {} - template: - metadata: - creationTimestamp: null - labels: - app: tracker-api - spec: - containers: - - image: gcr.io/track-compliance/api-js:master-15be6f4-1631797955 # {"$imagepolicy": "flux-system:api"} - name: api - ports: - - containerPort: 4000 - env: - - name: DB_PASS - valueFrom: - secretKeyRef: - name: api - key: DB_PASS - - name: DB_NAME - valueFrom: - secretKeyRef: - name: api - key: DB_NAME - - name: DB_URL - valueFrom: - secretKeyRef: - name: api - key: DB_URL - - name: CIPHER_KEY - valueFrom: - secretKeyRef: - name: api - key: CIPHER_KEY - - name: AUTHENTICATED_KEY - valueFrom: - secretKeyRef: - name: api - key: AUTHENTICATED_KEY - - name: REFRESH_KEY - valueFrom: - secretKeyRef: - name: api - key: REFRESH_KEY - - name: SIGN_IN_KEY - valueFrom: - secretKeyRef: - name: api - key: SIGN_IN_KEY - - name: AUTH_TOKEN_EXPIRY - valueFrom: - secretKeyRef: - name: api - key: AUTH_TOKEN_EXPIRY - - name: REFRESH_TOKEN_EXPIRY - valueFrom: - secretKeyRef: - name: api - key: REFRESH_TOKEN_EXPIRY - - name: NOTIFICATION_API_KEY - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_API_KEY - - name: NOTIFICATION_API_URL - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_API_URL - - name: DMARC_REPORT_API_URL - valueFrom: - secretKeyRef: - name: api - key: DMARC_REPORT_API_URL - - name: DMARC_REPORT_API_SECRET - valueFrom: - secretKeyRef: - name: api - key: DMARC_REPORT_API_SECRET - - name: DMARC_REPORT_API_TOKEN - valueFrom: - secretKeyRef: - name: api - key: DMARC_REPORT_API_TOKEN - - name: TOKEN_HASH - valueFrom: - secretKeyRef: - name: api - key: TOKEN_HASH - - name: DEPTH_LIMIT - valueFrom: - secretKeyRef: - name: api - key: DEPTH_LIMIT - - name: COST_LIMIT - valueFrom: - secretKeyRef: - name: api - key: COST_LIMIT - - name: SCALAR_COST - valueFrom: - secretKeyRef: - name: api - key: SCALAR_COST - - name: OBJECT_COST - valueFrom: - secretKeyRef: - name: api - key: OBJECT_COST - - name: LIST_FACTOR - valueFrom: - secretKeyRef: - name: api - key: OBJECT_COST - - name: DNS_SCANNER_ENDPOINT - valueFrom: - secretKeyRef: - name: api - key: DNS_SCANNER_ENDPOINT - - name: HTTPS_SCANNER_ENDPOINT - valueFrom: - secretKeyRef: - name: api - key: HTTPS_SCANNER_ENDPOINT - - name: SSL_SCANNER_ENDPOINT - valueFrom: - secretKeyRef: - name: api - key: SSL_SCANNER_ENDPOINT - - name: NOTIFICATION_AUTHENTICATE_EMAIL_ID - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_AUTHENTICATE_EMAIL_ID - - name: NOTIFICATION_AUTHENTICATE_TEXT_ID - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_AUTHENTICATE_TEXT_ID - - name: NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN - - name: NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR - - name: NOTIFICATION_ORG_INVITE_EN - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_ORG_INVITE_EN - - name: NOTIFICATION_ORG_INVITE_FR - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_ORG_INVITE_FR - - name: NOTIFICATION_PASSWORD_RESET_EN - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_PASSWORD_RESET_EN - - name: NOTIFICATION_PASSWORD_RESET_FR - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_PASSWORD_RESET_FR - - name: NOTIFICATION_TWO_FACTOR_CODE_EN - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_TWO_FACTOR_CODE_EN - - name: NOTIFICATION_TWO_FACTOR_CODE_FR - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_TWO_FACTOR_CODE_FR - - name: NOTIFICATION_VERIFICATION_EMAIL_EN - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_VERIFICATION_EMAIL_EN - - name: NOTIFICATION_VERIFICATION_EMAIL_FR - valueFrom: - secretKeyRef: - name: api - key: NOTIFICATION_VERIFICATION_EMAIL_FR - - name: TRACING_ENABLED - valueFrom: - secretKeyRef: - name: api - key: TRACING_ENABLED - - name: REDIS_PORT_NUMBER - valueFrom: - secretKeyRef: - name: api - key: REDIS_PORT_NUMBER - - name: REDIS_DOMAIN_NAME - valueFrom: - secretKeyRef: - name: api - key: REDIS_DOMAIN_NAME - - name: DKIM_SCAN_CHANNEL - valueFrom: - secretKeyRef: - name: api - key: DKIM_SCAN_CHANNEL - - name: DMARC_SCAN_CHANNEL - valueFrom: - secretKeyRef: - name: api - key: DMARC_SCAN_CHANNEL - - name: SPF_SCAN_CHANNEL - valueFrom: - secretKeyRef: - name: api - key: SPF_SCAN_CHANNEL - - name: SSL_SCAN_CHANNEL - valueFrom: - secretKeyRef: - name: api - key: SSL_SCAN_CHANNEL - - name: HTTPS_SCAN_CHANNEL - valueFrom: - secretKeyRef: - name: api - key: HTTPS_SCAN_CHANNEL - resources: - limits: - cpu: 300m - memory: 70Mi - requests: - cpu: 300m - memory: 70Mi -status: {} diff --git a/app/bases/tracker-api-service.yaml b/app/bases/tracker-api-service.yaml deleted file mode 100644 index e67e278c7f..0000000000 --- a/app/bases/tracker-api-service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: tracker-api - name: tracker-api - namespace: api -spec: - ports: - - name: http-api - port: 4000 - targetPort: 4000 - selector: - app: tracker-api - type: ClusterIP -status: - loadBalancer: {} diff --git a/app/bases/tracker-api-virtual-service.yaml b/app/bases/tracker-api-virtual-service.yaml deleted file mode 100644 index ca7a4be48f..0000000000 --- a/app/bases/tracker-api-virtual-service.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: VirtualService -metadata: - name: tracker-api-virtual-service - namespace: api -spec: - hosts: - - "*" - gateways: - - istio-system/publicgateway - http: - - name: gateway-to-api - match: - - uri: - prefix: /graphql - - uri: - prefix: /voyager - route: - - destination: - host: tracker-api.api.svc.cluster.local diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml deleted file mode 100644 index c46696004f..0000000000 --- a/app/bases/tracker-frontend-deployment.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app: tracker-frontend - name: tracker-frontend - namespace: frontend -spec: - replicas: 1 - selector: - matchLabels: - app: tracker-frontend - strategy: {} - template: - metadata: - labels: - app: tracker-frontend - spec: - containers: - - image: gcr.io/track-compliance/frontend:master-8f27d1b-1631563335 # {"$imagepolicy": "flux-system:frontend"} - name: frontend - resources: - limits: - cpu: 200m - memory: 45Mi - requests: - cpu: 200m - memory: 45Mi -status: {} diff --git a/app/bases/tracker-frontend-service.yaml b/app/bases/tracker-frontend-service.yaml deleted file mode 100644 index a53dd7be17..0000000000 --- a/app/bases/tracker-frontend-service.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: tracker-frontend - name: tracker-frontend - namespace: frontend -spec: - ports: - - name: http-frontend - port: 3000 - selector: - app: tracker-frontend - type: ClusterIP -status: - loadBalancer: {} diff --git a/app/creds/dev/README.md b/app/creds/dev/README.md deleted file mode 100644 index e2645a4a9d..0000000000 --- a/app/creds/dev/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Generate dev secrets - -This config is included in others (like minikube) and you probably don't need to run it directly. - -If you do need *just* the dev secrets, follow the tutorial in the root of the app directory and With the `.env` files in place, run the following command: - -``` -kustomize build app/creds/dev | kubectl apply -f - -``` diff --git a/app/creds/dev/kustomization.yaml b/app/creds/dev/kustomization.yaml deleted file mode 100644 index 348323303c..0000000000 --- a/app/creds/dev/kustomization.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -secretGenerator: -- envs: - - arangodb.env - name: arangodb - namespace: db -- envs: - - superadmin.env - name: superadmin - namespace: superadmin -- envs: - - api.env - name: api - namespace: api -- envs: - - scanners.env - name: scanners - namespace: scanners -- envs: - - dmarc.env - name: dmarc - namespace: scanners -generatorOptions: - disableNameSuffixHash: true -components: -- ../../namespaces diff --git a/app/creds/prod/README.md b/app/creds/prod/README.md deleted file mode 100644 index 66a28f5b80..0000000000 --- a/app/creds/prod/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Generate secrets - -Secrets handling for GKE is pretty basic at the moment. We seed the cluster at creation time with the secrets it needs. This overlay is doing just that. - -The kubernetes secrets are being generated from `.env` files. From the root directory you can create these files with the following commands: - -``` -cat <<'EOF' > app/generate-secrets/scanners.env -DB_PASS=test -DB_HOST=arangodb.db:8529 -DB_USER=root -DB_NAME=track_dmarc -SA_USER_NAME=superuser@department.gc.ca -SA_PASSWORD=superadminpassword -SA_DISPLAY_NAME=superuser -EOF -``` - -``` -cat <<'EOF' > app/generate-secrets/arangodb.env -username=root -password=test -EOF -``` - -```bash -cat <<'EOF' > app/generate-secrets/api.env -DB_PASS=test -DB_URL=http://arangodb.db:8529 -DB_NAME=track_dmarc -AUTHENTICATED_KEY=alonghash -SIGN_IN_KEY=alonghash -NOTIFICATION_API_KEY=test_key-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca -DMARC_REPORT_API_SECRET=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -TOKEN_HASH=somelonghash -DMARC_REPORT_API_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -DMARC_REPORT_API_URL=http://localhost:4001/graphql -DEPTH_LIMIT=15 -COST_LIMIT=5000 -SCALAR_COST=1 -OBJECT_COST=1 -LIST_FACTOR=1 -EOF -``` - -With the `.env` files in place, the Kubernetes config can be generated and applied with the following command: - -``` -kustomize build app/generate-secrets | kubectl apply -f - -``` diff --git a/app/creds/prod/kustomization.yaml b/app/creds/prod/kustomization.yaml deleted file mode 100644 index 348323303c..0000000000 --- a/app/creds/prod/kustomization.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -secretGenerator: -- envs: - - arangodb.env - name: arangodb - namespace: db -- envs: - - superadmin.env - name: superadmin - namespace: superadmin -- envs: - - api.env - name: api - namespace: api -- envs: - - scanners.env - name: scanners - namespace: scanners -- envs: - - dmarc.env - name: dmarc - namespace: scanners -generatorOptions: - disableNameSuffixHash: true -components: -- ../../namespaces diff --git a/app/gke/arangodb-deployment.yaml b/app/gke/arangodb-deployment.yaml deleted file mode 100644 index 72d7d28da0..0000000000 --- a/app/gke/arangodb-deployment.yaml +++ /dev/null @@ -1,16 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - agents: - volumeClaimTemplate: - spec: - storageClassName: slow-retain - persistentVolumeReclaimPolicy: Retain - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain diff --git a/app/gke/certificate.yaml b/app/gke/certificate.yaml deleted file mode 100644 index 7e0d4a072a..0000000000 --- a/app/gke/certificate.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ingress-cert - namespace: istio-system -spec: - commonName: tracker.alpha.canada.ca - dnsNames: - - tracker.alpha.canada.ca - - suivi.alpha.canada.ca - issuerRef: - kind: Issuer - name: letsencrypt - privateKey: - algorithm: RSA - encoding: PKCS8 # ITSP.40.062 6.2 Signature Algorithms - size: 4096 - secretName: tracker-credential -status: {} diff --git a/app/gke/kustomization.yaml b/app/gke/kustomization.yaml deleted file mode 100644 index f9e4bebee1..0000000000 --- a/app/gke/kustomization.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -patchesStrategicMerge: -- arangodb-deployment.yaml -- tracker-api-deployment.yaml -- tracker-frontend-deployment.yaml -- publicgateway.yaml -- certificate.yaml -replicas: -- name: tracker-frontend - count: 2 -- name: tracker-api - count: 2 -- name: arango-deployment-replication-operator - count: 1 -- name: arango-deployment-operator - count: 1 -- name: arango-storage-operator - count: 1 -components: -- ../namespaces diff --git a/app/gke/publicgateway.yaml b/app/gke/publicgateway.yaml deleted file mode 100644 index 5e569a900c..0000000000 --- a/app/gke/publicgateway.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: publicgateway - namespace: istio-system - labels: - istio: publicgateway -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - hosts: - - "*" - tls: - httpsRedirect: true # ITPIN 6.1.1 redirected from HTTP - - port: - number: 443 - name: https - protocol: HTTPS # ITPIN 6.1.1 is configured for HTTPS - hosts: - - "*" - tls: - mode: SIMPLE - credentialName: tracker-credential - privateKey: sds - serverCertificate: sds - minProtocolVersion: TLSV1_2 # ITPIN 6.1.3 implements TLS 1.2, or subsequent versions - cipherSuites: # ITPIN 6.1.3 uses supported cryptographic algorithms - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 diff --git a/app/gke/tracker-api-deployment.yaml b/app/gke/tracker-api-deployment.yaml deleted file mode 100644 index fec899a174..0000000000 --- a/app/gke/tracker-api-deployment.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app: tracker-api - name: tracker-api - namespace: api - annotations: - fluxcd.io/automated: "true" -spec: - selector: - matchLabels: - app: tracker-api - strategy: - rollingUpdate: - maxSurge: 50% - maxUnavailable: 50% - type: RollingUpdate - diff --git a/app/gke/tracker-frontend-deployment.yaml b/app/gke/tracker-frontend-deployment.yaml deleted file mode 100644 index ff6f88987f..0000000000 --- a/app/gke/tracker-frontend-deployment.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app: tracker-frontend - name: tracker-frontend - namespace: frontend - annotations: - fluxcd.io/automated: "true" -spec: - selector: - matchLabels: - app: tracker-frontend - strategy: - rollingUpdate: - maxSurge: 50% - maxUnavailable: 50% - type: RollingUpdate - diff --git a/app/jobs/README.md b/app/jobs/README.md deleted file mode 100644 index 0885306806..0000000000 --- a/app/jobs/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# One-off Jobs - -This directory contains manifests for the execution of one-off tasks that shouldn't be scheduled. - -E.g. Initiation of scans or core service outside of regularly scheduled execution. - -Deploying each file using `kubectl -n apply -f ` will create a Kubernetes job which will only be performed once. - - -## Additional considerations - -The naming scheme for these manifests should be -job.yaml. - -The image tagging for manifests within this directory is not updated/managed by flux, so tags may be out of date and require periodic updates to ensure proper functionality. diff --git a/app/jobs/backup/aks/backup-job.yaml b/app/jobs/backup/aks/backup-job.yaml deleted file mode 100755 index aa719764a8..0000000000 --- a/app/jobs/backup/aks/backup-job.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: backup-service - namespace: db - labels: - job: backup -spec: - template: - spec: - containers: - - name: upload - image: rclone/rclone - command: ["/bin/sh", "-c"] - args: ["rclone copy /tmp/dump :azureblob:dbdump/tracker-backup-$(date -I)"] - env: - - name: RCLONE_AZUREBLOB_ACCOUNT - valueFrom: - secretKeyRef: - name: az-dbdump-account - key: az-dbdump-account - - name: RCLONE_AZUREBLOB_KEY - valueFrom: - secretKeyRef: - name: az-dbdump-sak - key: az-dbdump-sak - volumeMounts: - - name: dump - mountPath: /tmp/dump - volumes: - - name: dump - emptyDir: {} - restartPolicy: Never diff --git a/app/jobs/backup/base/backup-job.yaml b/app/jobs/backup/base/backup-job.yaml deleted file mode 100755 index 32b7ad4704..0000000000 --- a/app/jobs/backup/base/backup-job.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: backup-service - namespace: db - labels: - job: backup -spec: - template: - spec: - serviceAccountName: backup-service - initContainers: - - name: dump - image: "arangodb:3.7.11" - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: arangodb - key: username - - name: DB_PASS - valueFrom: - secretKeyRef: - name: arangodb - key: password - args: - - "arangodump" - - "--server.endpoint=tcp://arangodb:8529" - - "--server.username=$(DB_USER)" - - "--server.password=$(DB_PASS)" - - "--server.database=track_dmarc" - - "--output-directory=/tmp/dump" - volumeMounts: - - name: dump - mountPath: /tmp/dump - containers: - volumes: - - name: dump - emptyDir: {} - restartPolicy: Never diff --git a/app/jobs/backup/gke/backup-job.yaml b/app/jobs/backup/gke/backup-job.yaml deleted file mode 100755 index b7c4b41ebe..0000000000 --- a/app/jobs/backup/gke/backup-job.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: backup-service - namespace: db - labels: - job: backup -spec: - template: - spec: - containers: - - name: upload - image: rclone/rclone - command: ["/bin/sh", "-c"] - args: ["rclone copy /tmp/dump :gcs:gc-tracker-backups/tracker-backup-$(date -I)"] - env: - - name: RCLONE_GCS_PROJECT_NUMBER - value: "958151870606" - - name: RCLONE_GCS_BUCKET_POLICY_ONLY - value: "true" - volumeMounts: - - name: dump - mountPath: /tmp/dump - volumes: - - name: dump - emptyDir: {} - restartPolicy: Never - diff --git a/app/jobs/backup/staging/backup-job.yaml b/app/jobs/backup/staging/backup-job.yaml deleted file mode 100755 index aa719764a8..0000000000 --- a/app/jobs/backup/staging/backup-job.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: backup-service - namespace: db - labels: - job: backup -spec: - template: - spec: - containers: - - name: upload - image: rclone/rclone - command: ["/bin/sh", "-c"] - args: ["rclone copy /tmp/dump :azureblob:dbdump/tracker-backup-$(date -I)"] - env: - - name: RCLONE_AZUREBLOB_ACCOUNT - valueFrom: - secretKeyRef: - name: az-dbdump-account - key: az-dbdump-account - - name: RCLONE_AZUREBLOB_KEY - valueFrom: - secretKeyRef: - name: az-dbdump-sak - key: az-dbdump-sak - volumeMounts: - - name: dump - mountPath: /tmp/dump - volumes: - - name: dump - emptyDir: {} - restartPolicy: Never diff --git a/app/jobs/backup/test/backup-job.yaml b/app/jobs/backup/test/backup-job.yaml deleted file mode 100755 index b7c4b41ebe..0000000000 --- a/app/jobs/backup/test/backup-job.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: backup-service - namespace: db - labels: - job: backup -spec: - template: - spec: - containers: - - name: upload - image: rclone/rclone - command: ["/bin/sh", "-c"] - args: ["rclone copy /tmp/dump :gcs:gc-tracker-backups/tracker-backup-$(date -I)"] - env: - - name: RCLONE_GCS_PROJECT_NUMBER - value: "958151870606" - - name: RCLONE_GCS_BUCKET_POLICY_ONLY - value: "true" - volumeMounts: - - name: dump - mountPath: /tmp/dump - volumes: - - name: dump - emptyDir: {} - restartPolicy: Never - diff --git a/app/jobs/backup/test/kustomization.yaml b/app/jobs/backup/test/kustomization.yaml deleted file mode 100644 index d389bc54aa..0000000000 --- a/app/jobs/backup/test/kustomization.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../base -patchesStrategicMerge: -- backup-job.yaml -# patchesJSON6902: -# - target: -# group: install.istio.io -# version: v1alpha1 -# kind: IstioOperator -# name: istio-controlplane -# namespace: istio-operator -# patch: |- -# - op: add -# path: /spec/components/ingressGateways/0/k8s/service/loadBalancerIP -# value: 10.58.10.58 -# - op: add -# path: /spec/components/ingressGateways/0/k8s/serviceAnnotations -# value: -# service.beta.kubernetes.io/azure-load-balancer-internal: "true" diff --git a/app/jobs/guidance-job.yaml b/app/jobs/guidance-job.yaml deleted file mode 100644 index 6e1a7c5bb7..0000000000 --- a/app/jobs/guidance-job.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: guidance-job -spec: - template: - spec: - containers: - - name: guidance - image: gcr.io/track-compliance/services/guidance:master-410a212-1628257658 # {"$imagepolicy": "flux-system:guidance"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - - name: REPO_NAME - value: "tracker" - - name: REPO_OWNER - value: "canada-ca" - - name: GUIDANCE_DIR - value: "guidance" - - name: GITHUB_TOKEN - valueFrom: - secretKeyRef: - name: scanners - key: GITHUB_TOKEN - restartPolicy: Never - backoffLimit: 4 diff --git a/app/jobs/scan-job.yaml b/app/jobs/scan-job.yaml deleted file mode 100644 index 3566efbd23..0000000000 --- a/app/jobs/scan-job.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: scan-job -spec: - template: - metadata: - labels: - app: scanners - role: scan-job - spec: - containers: - - name: scan - image: gcr.io/track-compliance/services/autoscan:master-b0e6547-1625576840 # {"$imagepolicy": "flux-system:autoscan"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - restartPolicy: Never - backoffLimit: 4 diff --git a/app/jobs/summaries-job.yaml b/app/jobs/summaries-job.yaml deleted file mode 100644 index 179ee01d6f..0000000000 --- a/app/jobs/summaries-job.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: summaries-job -spec: - template: - spec: - containers: - - name: summaries - image: gcr.io/track-compliance/services/summaries:master-8d3a6a0-1628271814 # {"$imagepolicy": "flux-system:summaries"} - env: - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_HOST - valueFrom: - secretKeyRef: - name: scanners - key: DB_HOST - - name: DB_PORT - value: "8529" - - name: DB_NAME - valueFrom: - secretKeyRef: - name: scanners - key: DB_NAME - restartPolicy: Never - backoffLimit: 4 diff --git a/app/jobs/super-admin.yaml b/app/jobs/super-admin.yaml deleted file mode 100644 index 0f2729e513..0000000000 --- a/app/jobs/super-admin.yaml +++ /dev/null @@ -1,75 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: super-admin - namespace: superadmin -spec: - template: - spec: - containers: - - name: super-admin - image: gcr.io/track-compliance/super-admin:master-0528bb8-1631794641 # {"$imagepolicy": "flux-system:super-admin"} - env: - - name: DB_PASS - valueFrom: - secretKeyRef: - name: superadmin - key: DB_PASS - - name: DB_NAME - valueFrom: - secretKeyRef: - name: superadmin - key: DB_NAME - - name: DB_URL - value: http://arangodb.db:8529 - - name: SA_USER_DISPLAY_NAME - valueFrom: - secretKeyRef: - name: superadmin - key: SA_USER_DISPLAY_NAME - - name: SA_USER_USERNAME - valueFrom: - secretKeyRef: - name: superadmin - key: SA_USER_USERNAME - - name: SA_USER_PASSWORD - valueFrom: - secretKeyRef: - name: superadmin - key: SA_USER_PASSWORD - - name: SA_USER_LANG - value: "english" - - name: SA_ORG_EN_SLUG - value: "sa" - - name: SA_ORG_EN_ACRONYM - value: "SA" - - name: SA_ORG_EN_NAME - value: "Super Admin" - - name: SA_ORG_EN_ZONE - value: "FED" - - name: SA_ORG_EN_SECTOR - value: "TBS" - - name: SA_ORG_EN_PROVINCE - value: "Ontario" - - name: SA_ORG_EN_CITY - value: "Ottawa" - - name: SA_ORG_EN_COUNTRY - value: "Canada" - - name: SA_ORG_FR_SLUG - value: "sa" - - name: SA_ORG_FR_ACRONYM - value: "SA" - - name: SA_ORG_FR_NAME - value: "Super Admin" - - name: SA_ORG_FR_ZONE - value: "FED" - - name: SA_ORG_FR_SECTOR - value: "TBS" - - name: SA_ORG_FR_PROVINCE - value: "Ontario" - - name: SA_ORG_FR_CITY - value: "Ottawa" - - name: SA_ORG_FR_COUNTRY - value: "Canada" - restartPolicy: Never - backoffLimit: 4 diff --git a/app/minikube/arangodb-deployment.yaml b/app/minikube/arangodb-deployment.yaml deleted file mode 100644 index 7063f60f2b..0000000000 --- a/app/minikube/arangodb-deployment.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: database.arangodb.com/v1alpha -kind: ArangoDeployment -metadata: - name: arangodb - namespace: db -spec: - metadata: - image: arangodb/arangodb:3.7.3 - environment: Development - mode: Single - tls: - caSecretName: None - externalAccess: - type: None - bootstrap: - passwordSecretNames: - root: arangodb - metrics: - enabled: true - coordinators: - count: 1 - agents: - count: 1 - dbservers: - count: 1 diff --git a/app/minikube/kustomization.yaml b/app/minikube/kustomization.yaml deleted file mode 100644 index 73aa5dae88..0000000000 --- a/app/minikube/kustomization.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -patchesStrategicMerge: -- arangodb-deployment.yaml -replicas: -- name: tracker-frontend - count: 1 -- name: tracker-api - count: 1 -- name: arango-deployment-replication-operator - count: 1 -- name: arango-deployment-operator - count: 1 -- name: arango-storage-operator - count: 1 -components: -- ../namespaces diff --git a/app/namespaces/db-namespace.yaml b/app/namespaces/db-namespace.yaml deleted file mode 100644 index f74356e671..0000000000 --- a/app/namespaces/db-namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: db -spec: {} -status: {} diff --git a/app/namespaces/kustomization.yaml b/app/namespaces/kustomization.yaml deleted file mode 100644 index 3972fbb30a..0000000000 --- a/app/namespaces/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component -resources: -- api-namespace.yaml -- db-namespace.yaml -- frontend-namespace.yaml -- scanners-namespace.yaml -- sa-namespace.yaml diff --git a/app/namespaces/sa-namespace.yaml b/app/namespaces/sa-namespace.yaml deleted file mode 100644 index 34fc695458..0000000000 --- a/app/namespaces/sa-namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: superadmin -spec: {} -status: {} diff --git a/app/namespaces/scanners-namespace.yaml b/app/namespaces/scanners-namespace.yaml deleted file mode 100644 index 9877cfbc12..0000000000 --- a/app/namespaces/scanners-namespace.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Namespace -apiVersion: v1 -kind: Namespace -metadata: - name: scanners - labels: - istio-injection: disabled diff --git a/app/production/README.md b/app/production/README.md deleted file mode 100644 index 98c7f57624..0000000000 --- a/app/production/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Production on AKS - -The purpose of this overlay is to bring up Tracker on AKS in a production config. The config here is just a draft. We're just getting started! - -If you need some credentials for this cluster, you can generate them with `make credentials mode=prod`. - -## Bringing up a cluster on AKS - -Normally we would create the cluster with `make cluster`, but we haven't gotten an AKS equivalent worked out yet. In the absence of that, we'll just assume you already have a 6 node cluster somehow, and `kubectl` configured to talk to it. - -## Installing Tracker - -Just run the following commands. - -```sh -make secrets env=aks -# If either of these fail, just run them again -make platform env=production -make app env=production -``` -Or as an alternate path, you could install flux and let it do the work for you. - -```sh -make secrets env=aks -make deploy env=production -``` - -If you want to watch the pod creation process, you can do it with this: - -```sh -watch kubectl get po -A -``` - -That will bring the cluster up with a self-signed certificate. To connect to it, we just need the external IP. - -```sh -kubectl get svc -n istio-system istio-ingressgateway -``` - -Connecting to both `https://` and `https:///graphql` should succeed. Reaching the frontend and API respectively. - -## Loading data - -The database isn't exposed to the outside world, so loading data requires you to forward the database ports to your local machine. - -```sh -kubectl port-forward -n db svc/arangodb 8529:8529 -``` - -With that port forwarding in place, you can now load/dump with the following commands: - -```sh -make dump to=~/track_dmarc-$(date --iso-8601) -make restore from=~/track_dmarc-2021-05-12 -``` diff --git a/app/production/arangodb-deployment.yaml b/app/production/arangodb-deployment.yaml deleted file mode 100644 index f337749af9..0000000000 --- a/app/production/arangodb-deployment.yaml +++ /dev/null @@ -1,17 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - agents: - volumeClaimTemplate: - spec: - storageClassName: slow-retain - persistentVolumeReclaimPolicy: Retain - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain - diff --git a/app/production/certificate.yaml b/app/production/certificate.yaml deleted file mode 100644 index 07455fde69..0000000000 --- a/app/production/certificate.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ingress-cert - namespace: istio-system -spec: - commonName: tracker.canada.ca - dnsNames: - - tracker.canada.ca - - suivi.canada.ca - issuerRef: - kind: Issuer - name: letsencrypt-staging - privateKey: - algorithm: RSA - encoding: PKCS8 # ITSP.40.062 6.2 Signature Algorithms - size: 4096 - secretName: tracker-credential -status: {} diff --git a/app/production/knative/config/queues.yaml b/app/production/knative/config/queues.yaml deleted file mode 100644 index 06758d6fe5..0000000000 --- a/app/production/knative/config/queues.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: scan-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "2" - spec: - timeoutSeconds: 500 - containers: - - name: scan-queue - image: gcr.io/track-compliance/services/scan-queue - ---- - -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: result-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "2" - spec: - timeoutSeconds: 500 - containers: - - name: result-queue - image: gcr.io/track-compliance/services/result-queue diff --git a/app/production/kustomization.yaml b/app/production/kustomization.yaml deleted file mode 100644 index d47874cb81..0000000000 --- a/app/production/kustomization.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -patchesStrategicMerge: -- arangodb-deployment.yaml -- certificate.yaml -- publicgateway.yaml -- knative/config/queues.yaml -replicas: -- count: 2 - name: tracker-frontend -- count: 2 - name: tracker-api -- count: 1 - name: arango-deployment-replication-operator -- count: 1 - name: arango-deployment-operator -- count: 1 - name: arango-storage-operator -components: -- ../namespaces diff --git a/app/production/publicgateway.yaml b/app/production/publicgateway.yaml deleted file mode 100644 index 1882cd7a02..0000000000 --- a/app/production/publicgateway.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: publicgateway - namespace: istio-system - labels: - istio: publicgateway -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - hosts: - - "*" - tls: - httpsRedirect: true - - port: - number: 443 - name: https - protocol: HTTPS - hosts: - - "*" - tls: - mode: SIMPLE - credentialName: ingress-cert-frompfx - privateKey: sds - serverCertificate: sds - minProtocolVersion: TLSV1_2 - cipherSuites: - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 diff --git a/app/production/storage-classes.yaml b/app/production/storage-classes.yaml deleted file mode 100644 index 642902e863..0000000000 --- a/app/production/storage-classes.yaml +++ /dev/null @@ -1,50 +0,0 @@ -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: fast-retain -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Retain -parameters: - storageaccounttype: Premium_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: slow-retain -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Retain -parameters: - storageaccounttype: Standard_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: fast-delete -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Delete -parameters: - storageaccounttype: Premium_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: slow-delete -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Delete -parameters: - storageaccounttype: Standard_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer ---- diff --git a/app/staging/README.md b/app/staging/README.md deleted file mode 100644 index d0bcaa7016..0000000000 --- a/app/staging/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Staging on AKS - -The purpose of this overlay is to bring up Tracker on AKS in a Staging config. The config here is just a draft. We're just getting started! - -If you need some dev credentials for this test cluster, you can generate them with `make credentials mode=dev`. - -## Bringing up a cluster on AKS - -Normally we would create the cluster with `make cluster`, but we haven't gotten an AKS equivalent worked out yet. In the absence of that, we'll just assume you already have a 6 node cluster somehow, and `kubectl` configured to talk to it. - -## Installing Tracker - -Just run the following commands. - -```sh -make secrets env=aks -# If either of these fail, just run them again -make platform env=staging -make app env=staging -``` -Or as an alternate path, you could install flux and let it do the work for you. - -```sh -make secrets env=aks -make deploy env=staging -``` - -If you want to watch the pod creation process, you can do it with this: - -```sh -watch kubectl get po -A -``` - -That will bring the cluster up with a self-signed certificate. To connect to it, we just need the external IP. - -```sh -kubectl get svc -n istio-system istio-ingressgateway -``` - -Connecting to both `https://` and `https:///graphql` should succeed. Reaching the frontend and API respectively. - -## Loading data - -The database isn't exposed to the outside world, so loading data requires you to forward the database ports to your local machine. - -```sh -kubectl port-forward -n db svc/arangodb 8529:8529 -``` - -With that port forwarding in place, you can now load/dump with the following commands: - -```sh -make backup to=~/track_dmarc-$(date --iso-8601) -make restore from=~/track_dmarc-2021-05-12 -``` diff --git a/app/staging/arangodb-deployment.yaml b/app/staging/arangodb-deployment.yaml deleted file mode 100644 index f337749af9..0000000000 --- a/app/staging/arangodb-deployment.yaml +++ /dev/null @@ -1,17 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - agents: - volumeClaimTemplate: - spec: - storageClassName: slow-retain - persistentVolumeReclaimPolicy: Retain - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain - diff --git a/app/staging/certificate.yaml b/app/staging/certificate.yaml deleted file mode 100644 index 07455fde69..0000000000 --- a/app/staging/certificate.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ingress-cert - namespace: istio-system -spec: - commonName: tracker.canada.ca - dnsNames: - - tracker.canada.ca - - suivi.canada.ca - issuerRef: - kind: Issuer - name: letsencrypt-staging - privateKey: - algorithm: RSA - encoding: PKCS8 # ITSP.40.062 6.2 Signature Algorithms - size: 4096 - secretName: tracker-credential -status: {} diff --git a/app/staging/knative/config/queues.yaml b/app/staging/knative/config/queues.yaml deleted file mode 100644 index 06758d6fe5..0000000000 --- a/app/staging/knative/config/queues.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: scan-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "2" - spec: - timeoutSeconds: 500 - containers: - - name: scan-queue - image: gcr.io/track-compliance/services/scan-queue - ---- - -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: result-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "2" - spec: - timeoutSeconds: 500 - containers: - - name: result-queue - image: gcr.io/track-compliance/services/result-queue diff --git a/app/staging/kustomization.yaml b/app/staging/kustomization.yaml deleted file mode 100644 index d47874cb81..0000000000 --- a/app/staging/kustomization.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -patchesStrategicMerge: -- arangodb-deployment.yaml -- certificate.yaml -- publicgateway.yaml -- knative/config/queues.yaml -replicas: -- count: 2 - name: tracker-frontend -- count: 2 - name: tracker-api -- count: 1 - name: arango-deployment-replication-operator -- count: 1 - name: arango-deployment-operator -- count: 1 - name: arango-storage-operator -components: -- ../namespaces diff --git a/app/staging/publicgateway.yaml b/app/staging/publicgateway.yaml deleted file mode 100644 index 1882cd7a02..0000000000 --- a/app/staging/publicgateway.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: publicgateway - namespace: istio-system - labels: - istio: publicgateway -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - hosts: - - "*" - tls: - httpsRedirect: true - - port: - number: 443 - name: https - protocol: HTTPS - hosts: - - "*" - tls: - mode: SIMPLE - credentialName: ingress-cert-frompfx - privateKey: sds - serverCertificate: sds - minProtocolVersion: TLSV1_2 - cipherSuites: - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 diff --git a/app/staging/storage-classes.yaml b/app/staging/storage-classes.yaml deleted file mode 100644 index 642902e863..0000000000 --- a/app/staging/storage-classes.yaml +++ /dev/null @@ -1,50 +0,0 @@ -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: fast-retain -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Retain -parameters: - storageaccounttype: Premium_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: slow-retain -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Retain -parameters: - storageaccounttype: Standard_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: fast-delete -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Delete -parameters: - storageaccounttype: Premium_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer - ---- - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: slow-delete -provisioner: kubernetes.io/azure-disk -reclaimPolicy: Delete -parameters: - storageaccounttype: Standard_LRS - kind: Managed -volumeBindingMode: WaitForFirstConsumer ---- diff --git a/app/test/README.md b/app/test/README.md deleted file mode 100644 index b1fb11d405..0000000000 --- a/app/test/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Test - -The purpose of this overlay is to bring up a "non-prod" copy of the full application on GKE, for... you guessed it, testing purposes! This configuration will come up using a self signed certificate, but other than that it should be almost identical to production. - -Since testing usually involves trying stuff that isn't committed to master yet, the commands to get things working are a little different from a normal deployment. - -If you need some dev credentials for this test cluster, you can generate them with `make credentials mode=dev`. - -## Bringing up a test cluster on GKE - -Currently we are just creating the cluster with the following command. - - -```sh -make cluster -make secrets env=test -# If either of these fail, just run them again -make platform env=test -make app env=test -``` - -If you want to watch the pod creation process, you can do it with this: - -```sh -watch kubectl get po -A -``` - -That will bring the cluster up with a self-signed certificate. To connect to it, we just need the external IP. - -```sh -kubectl get svc -n istio-system istio-ingressgateway -``` - -Connecting to both `https://` and `https:///graphql` should succeed. Reaching the frontend and API respectively. - -## Loading data - -The database isn't exposed to the outside world, so loading data requires you to forward the database ports to your local machine. - -```sh -kubectl port-forward -n db svc/arangodb 8529:8529 -``` - -With that port forwarding in place, you can now load/dump with the following commands: - -```sh -arangodump --server.database track_dmarc --output-directory track_dmarc-$(date --iso-8601) -arangorestore --create-database --server.database track_dmarc --input-directory track_dmarc-2021-05-12 -``` diff --git a/app/test/knative/config/queues.yaml b/app/test/knative/config/queues.yaml deleted file mode 100644 index 06758d6fe5..0000000000 --- a/app/test/knative/config/queues.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: scan-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "2" - spec: - timeoutSeconds: 500 - containers: - - name: scan-queue - image: gcr.io/track-compliance/services/scan-queue - ---- - -apiVersion: serving.knative.dev/v1 # Current version of Knative -kind: Service -metadata: - name: result-queue - namespace: scanners - labels: - app: scanners -spec: - template: - metadata: - annotations: - prometheus.io/scrape: 'true' - prometheus.io/port: '9090' - # Knative concurrency-based autoscaling (default). - autoscaling.knative.dev/class: kpa.autoscaling.knative.dev - autoscaling.knative.dev/metric: concurrency - autoscaling.knative.dev/minScale: "1" - autoscaling.knative.dev/maxScale: "2" - spec: - timeoutSeconds: 500 - containers: - - name: result-queue - image: gcr.io/track-compliance/services/result-queue diff --git a/app/test/kustomization.yaml b/app/test/kustomization.yaml deleted file mode 100644 index 0cdf779838..0000000000 --- a/app/test/kustomization.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -patchesStrategicMerge: -- publicgateway.yaml -- backup-service-account.yaml -- knative/config/queues.yaml -replicas: -- count: 2 - name: tracker-frontend -- count: 2 - name: tracker-api -- count: 1 - name: arango-deployment-replication-operator -- count: 1 - name: arango-deployment-operator -- count: 1 - name: arango-storage-operator -components: -- ../namespaces diff --git a/app/test/publicgateway.yaml b/app/test/publicgateway.yaml deleted file mode 100644 index 1aa2ebb443..0000000000 --- a/app/test/publicgateway.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: networking.istio.io/v1alpha3 -kind: Gateway -metadata: - name: publicgateway - namespace: istio-system - labels: - istio: publicgateway -spec: - selector: - istio: ingressgateway - servers: - - port: - number: 80 - name: http - protocol: HTTP - hosts: - - "*" - tls: - httpsRedirect: true - - port: - number: 443 - name: https - protocol: HTTPS - hosts: - - "*" - tls: - mode: SIMPLE - credentialName: tracker-credential - privateKey: sds - serverCertificate: sds - minProtocolVersion: TLSV1_2 - cipherSuites: - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 diff --git a/azure-defender-easm/README.md b/azure-defender-easm/README.md new file mode 100644 index 0000000000..b41c60e8aa --- /dev/null +++ b/azure-defender-easm/README.md @@ -0,0 +1,12 @@ +# Azure EASM + +This is an optional service that links Tracker with the [Microsoft Defender External Attack Surface Management (EASM)](https://learn.microsoft.com/en-us/azure/external-attack-surface-management/). This allows users and service admins to gain new insight into their assets, including asset discovery and CVE detection. + +## Running it + +``` +pipenv install +pipenv run service +``` + +## TODO diff --git a/azure-defender-easm/add-domain-to-easm/.dockerignore b/azure-defender-easm/add-domain-to-easm/.dockerignore new file mode 100644 index 0000000000..c2440b7a4f --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +tests +*.yaml +**/*.env diff --git a/azure-defender-easm/add-domain-to-easm/.env.example b/azure-defender-easm/add-domain-to-easm/.env.example new file mode 100644 index 0000000000..70addcfc55 --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/.env.example @@ -0,0 +1,11 @@ +SUBSCRIPTION_ID= +WORKSPACE_NAME= +RESOURCE_GROUP= +REGION= + +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= + +KUSTO_CLUSTER= +KUSTO_DATABASE= \ No newline at end of file diff --git a/azure-defender-easm/add-domain-to-easm/Dockerfile b/azure-defender-easm/add-domain-to-easm/Dockerfile new file mode 100644 index 0000000000..314f443b13 --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/Dockerfile @@ -0,0 +1,41 @@ +FROM python:3.14.3-alpine AS python-builder + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /working/install + +RUN apk add --no-cache \ + python3 \ + py3-pip \ + py3-setuptools \ + py3-wheel \ + build-base \ + python3-dev + +COPY requirements.txt /requirements.txt +# Install python requirements to /working/install directory for cleaner copy +RUN pip3 install --prefix=/working/install -r /requirements.txt + +#=============================================================================================== +#=============================================================================================== + +FROM python:3.14.3-alpine + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /add-domain-to-easm + +# Copy installed python modules +COPY --from=python-builder /working/install/lib /usr/local/lib + +COPY service.py ./ +COPY clients ./clients + +RUN adduser -D defender +USER defender + +CMD ["python3", "service.py"] diff --git a/azure-defender-easm/add-domain-to-easm/README.md b/azure-defender-easm/add-domain-to-easm/README.md new file mode 100644 index 0000000000..b41c60e8aa --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/README.md @@ -0,0 +1,12 @@ +# Azure EASM + +This is an optional service that links Tracker with the [Microsoft Defender External Attack Surface Management (EASM)](https://learn.microsoft.com/en-us/azure/external-attack-surface-management/). This allows users and service admins to gain new insight into their assets, including asset discovery and CVE detection. + +## Running it + +``` +pipenv install +pipenv run service +``` + +## TODO diff --git a/scanners/https-processor/__init__.py b/azure-defender-easm/add-domain-to-easm/clients/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from scanners/https-processor/__init__.py rename to azure-defender-easm/add-domain-to-easm/clients/__init__.py diff --git a/azure-defender-easm/add-domain-to-easm/clients/easm_client.py b/azure-defender-easm/add-domain-to-easm/clients/easm_client.py new file mode 100644 index 0000000000..d786242f4d --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/clients/easm_client.py @@ -0,0 +1,53 @@ +import os +import logging +from azure.identity import ClientSecretCredential +from azure.defender.easm import EasmClient +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +SUB_ID = os.getenv("SUBSCRIPTION_ID") +WORKSPACE_NAME = os.getenv("WORKSPACE_NAME") +RESOURCE_GROUP = os.getenv("RESOURCE_GROUP") +REGION = os.getenv("REGION") +ENDPOINT = f"{REGION}.easm.defender.microsoft.com" + +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +TENANT_ID = os.getenv("TENANT_ID") +CREDENTIAL = ClientSecretCredential(TENANT_ID, CLIENT_ID, CLIENT_SECRET) + +EASM_CLIENT = EasmClient(ENDPOINT, RESOURCE_GROUP, SUB_ID, WORKSPACE_NAME, CREDENTIAL) + + +def run_disco_group(group_name): + org_disco_group = get_disco_group(group_name) + if org_disco_group: + logger.info(f"Running discovery group {group_name}") + EASM_CLIENT.discovery_groups.run(group_name) + + +def list_disco_group_runs(group_name): + org_disco_group = get_disco_group(group_name) + if org_disco_group: + return EASM_CLIENT.discovery_groups.list_runs(group_name) + + +def get_disco_group(group_name): + disco_group = EASM_CLIENT.discovery_groups.get(group_name) + return disco_group + + +def create_disco_group(name, assets, frequency=0): + request = { + "description": "Discovery group made for discovering assets for " + name, + "seeds": assets, + "frequencyMilliseconds": frequency, + } + response = EASM_CLIENT.discovery_groups.put(name, request) + return response + + +def delete_disco_group(group_name): + EASM_CLIENT.discovery_groups.delete(group_name) diff --git a/azure-defender-easm/add-domain-to-easm/clients/kusto_client.py b/azure-defender-easm/add-domain-to-easm/clients/kusto_client.py new file mode 100644 index 0000000000..187c2bfd3c --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/clients/kusto_client.py @@ -0,0 +1,37 @@ +from azure.kusto.data import KustoClient, KustoConnectionStringBuilder +from azure.kusto.data.helpers import dataframe_from_result_table + +import os +from dotenv import load_dotenv + +load_dotenv() + +KUSTO_CLUSTER = os.getenv("KUSTO_CLUSTER") +REGION = os.getenv("REGION") +KUSTO_DATABASE = os.getenv("KUSTO_DATABASE") +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +TENANT_ID = os.getenv("TENANT_ID") + +KCSB_DATA = KustoConnectionStringBuilder.with_aad_application_key_authentication( + f"https://{KUSTO_CLUSTER}.{REGION}.kusto.windows.net", + CLIENT_ID, + CLIENT_SECRET, + TENANT_ID, +) +KUSTO_CLIENT = KustoClient(KCSB_DATA) + + +def get_host_asset(host_name): + query = f""" + declare query_parameters(hostName:string = '{host_name}'); + EasmHostAsset + | where Host == hostName + | limit 1 + | project Host, AssetUuid + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data diff --git a/azure-defender-easm/add-domain-to-easm/cloudbuild.yaml b/azure-defender-easm/add-domain-to-easm/cloudbuild.yaml new file mode 100644 index 0000000000..62ae4ce412 --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/cloudbuild.yaml @@ -0,0 +1,34 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + id: generate-image-name + entrypoint: "bash" + dir: azure-defender-easm/add-domain-to-easm + args: + - "-c" + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/azure-defender-easm/add-domain-to-easm:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: "gcr.io/cloud-builders/docker" + id: build + entrypoint: "bash" + dir: azure-defender-easm/add-domain-to-easm + args: + - "-c" + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: "gcr.io/cloud-builders/docker" + id: push-if-master + entrypoint: "bash" + dir: azure-defender-easm/add-domain-to-easm + args: + - "-c" + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi diff --git a/azure-defender-easm/add-domain-to-easm/requirements.txt b/azure-defender-easm/add-domain-to-easm/requirements.txt new file mode 100644 index 0000000000..25740cf112 --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/requirements.txt @@ -0,0 +1,7 @@ +azure-identity==1.16.1 +azure-defender-easm==1.0.0b1 +azure-kusto-data==4.3.1 +python-dotenv==1.1.0 +nats-py==2.6.0 +numpy==2.4.4 +pandas==3.0.2 \ No newline at end of file diff --git a/azure-defender-easm/add-domain-to-easm/service.py b/azure-defender-easm/add-domain-to-easm/service.py new file mode 100644 index 0000000000..a60edc50a9 --- /dev/null +++ b/azure-defender-easm/add-domain-to-easm/service.py @@ -0,0 +1,130 @@ +import logging +import os +from dataclasses import dataclass + +from dotenv import load_dotenv + +import asyncio +import nats +import json +import signal + +from nats.errors import TimeoutError as NatsTimeoutError +from nats.js import JetStreamContext +from nats.js.api import RetentionPolicy, ConsumerConfig, AckPolicy + +load_dotenv() + +logging.basicConfig( + level=logging.INFO, format="[%(asctime)s :: %(name)s :: %(levelname)s] %(message)s" +) +logger = logging.getLogger(__name__) + +from clients.easm_client import run_disco_group, create_disco_group +from clients.kusto_client import get_host_asset + +NAME = os.getenv("NAME", "add-domain-to-easm") +SERVERLIST = os.getenv("NATS_SERVERS", "nats://localhost:4222") +SERVERS = SERVERLIST.split(",") + + +async def run(): + loop = asyncio.get_running_loop() + + @dataclass + class Context: + should_exit: bool = False + sub: JetStreamContext.PullSubscription = None + + context = Context() + + async def error_cb(error): + logger.error(f"Uncaught error in callback: {error}") + + async def reconnected_cb(): + logger.info(f"Reconnected to NATS at {nc.connected_url.netloc}...") + # Ensure jetstream consumer is still present + context.sub = await js.pull_subscribe(**pull_subscribe_options) + + nc = await nats.connect( + error_cb=error_cb, + reconnected_cb=reconnected_cb, + servers=SERVERS, + name=NAME, + ) + + js = nc.jetstream() + logger.info(f"Connected to NATS at {nc.connected_url.netloc}...") + + pull_subscribe_options = { + "stream": "SCANS", + "subject": "scans.add_domain_to_easm", + "durable": "add_domain_to_easm", + "config": ConsumerConfig( + ack_policy=AckPolicy.EXPLICIT, + max_deliver=1, + max_waiting=100_000, + ack_wait=90, + ), + } + + context.sub = await js.pull_subscribe(**pull_subscribe_options) + + async def ask_exit(sig_name): + if context.should_exit is True: + return + logger.error(f"Got signal {sig_name}: exit") + context.should_exit = True + + for signal_name in {"SIGINT", "SIGTERM"}: + loop.add_signal_handler( + getattr(signal, signal_name), + lambda: asyncio.create_task(ask_exit(signal_name)), + ) + + while True: + if context.should_exit: + break + if nc.is_closed: + logger.error("Connection to NATS is closed") + + try: + logger.debug("Fetching message...") + msgs = await context.sub.fetch(batch=1, timeout=1) + msg = msgs[0] + except NatsTimeoutError: + logger.debug("No messages available...") + continue + + subject = msg.subject + reply = msg.reply + data = msg.data.decode() + logger.info(f"Received a message on '{subject} {reply}': {data}") + payload = json.loads(msg.data) + + domain = payload.get("domain") + if not domain.endswith(".gc.ca") and not domain.endswith(".canada.ca"): + logger.info(f"Skipping '{domain}' as it is not a GC domain.") + return + + try: + easm_asset = get_host_asset(domain) + if len(easm_asset) > 0: + logger.info(f"Skipping '{domain}' as it already exists in EASM.") + return + except Exception as e: + logger.error(f"Checking if asset exists in EASM: {str(e)}") + return + + try: + logger.info(f"Adding '{domain}' to EASM tooling...") + create_disco_group(name=domain, assets=[{"kind": "host", "name": domain}]) + run_disco_group(domain) + logger.info(f"Successfully added '{domain}' to EASM tooling.") + except Exception as e: + logger.error(f"Scanning subdomains: {str(e)}") + return + + +if __name__ == "__main__": + asyncio.run(run()) diff --git a/azure-defender-easm/add-easm-assets-to-tracker/.dockerignore b/azure-defender-easm/add-easm-assets-to-tracker/.dockerignore new file mode 100644 index 0000000000..c2440b7a4f --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +tests +*.yaml +**/*.env diff --git a/azure-defender-easm/add-easm-assets-to-tracker/.env.example b/azure-defender-easm/add-easm-assets-to-tracker/.env.example new file mode 100644 index 0000000000..9cc79dfe3d --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/.env.example @@ -0,0 +1,17 @@ +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= + +REGION= +KUSTO_CLUSTER= +KUSTO_DATABASE= + +DB_USER= +DB_PASS= +DB_URL= +DB_NAME= +UNCLAIMED_ID= +SERVICE_ACCOUNT_EMAIL= + +NATS_URL= +PUBLISH_TO= \ No newline at end of file diff --git a/azure-defender-easm/add-easm-assets-to-tracker/Dockerfile b/azure-defender-easm/add-easm-assets-to-tracker/Dockerfile new file mode 100644 index 0000000000..007d38947f --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/Dockerfile @@ -0,0 +1,41 @@ +FROM python:3.14.3-alpine AS python-builder + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /working/install + +RUN apk add --no-cache \ + python3 \ + py3-pip \ + py3-setuptools \ + py3-wheel \ + build-base \ + python3-dev + +COPY requirements.txt /requirements.txt +# Install python requirements to /working/install directory for cleaner copy +RUN pip3 install --prefix=/working/install -r /requirements.txt + +#=============================================================================================== +#=============================================================================================== + +FROM python:3.14.3-alpine + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /add-easm-assets-to-tracker + +# Copy installed python modules +COPY --from=python-builder /working/install/lib /usr/local/lib + +COPY service.py ./ +COPY clients ./clients + +RUN adduser -D defender +USER defender + +CMD ["python3", "service.py"] diff --git a/azure-defender-easm/add-easm-assets-to-tracker/README.md b/azure-defender-easm/add-easm-assets-to-tracker/README.md new file mode 100644 index 0000000000..b7512a8c22 --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/README.md @@ -0,0 +1,5 @@ +# Azure EASM + +This is an optional service that links Tracker with the [Microsoft Defender External Attack Surface Management (EASM)](https://learn.microsoft.com/en-us/azure/external-attack-surface-management/). This allows users and service admins to gain new insight into their assets, including asset discovery and CVE detection. + +## TODO diff --git a/scanners/https-scanner/__init__.py b/azure-defender-easm/add-easm-assets-to-tracker/clients/__init__.py similarity index 100% rename from scanners/https-scanner/__init__.py rename to azure-defender-easm/add-easm-assets-to-tracker/clients/__init__.py diff --git a/azure-defender-easm/add-easm-assets-to-tracker/clients/kusto_client.py b/azure-defender-easm/add-easm-assets-to-tracker/clients/kusto_client.py new file mode 100644 index 0000000000..1fd7c205e3 --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/clients/kusto_client.py @@ -0,0 +1,74 @@ +from azure.kusto.data import KustoClient, KustoConnectionStringBuilder +from azure.kusto.data.helpers import dataframe_from_result_table + +import logging +import os +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +KUSTO_CLUSTER = os.getenv("KUSTO_CLUSTER") +REGION = os.getenv("REGION") +KUSTO_DATABASE = os.getenv("KUSTO_DATABASE") +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +TENANT_ID = os.getenv("TENANT_ID") + +KCSB_DATA = KustoConnectionStringBuilder.with_aad_application_key_authentication( + f"https://{KUSTO_CLUSTER}.{REGION}.kusto.windows.net", + CLIENT_ID, + CLIENT_SECRET, + TENANT_ID, +) +KUSTO_CLIENT = KustoClient(KCSB_DATA) + + +def get_labelled_org_assets_from_org_key(org_key): + query = f""" + declare query_parameters(orgKey:string = '["{org_key}"]'); + EasmAsset + | where TimeGeneratedValue > ago(24h) + | where AssetLastSeen > ago(30d) + | where AssetType == 'HOST' + | where Labels == orgKey + | where AssetName !startswith '*.' + | join kind=inner EasmHostAsset on AssetName + | where TimeGeneratedValue > ago(24h) + | where Cnames == '[]' + | summarize by AssetName + """ + try: + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return [asset["AssetName"] for asset in data] + except Exception as e: + logger.error(f"Failed to get labelled assets from org key: {e}") + return [] + + +def get_unlabelled_assets(): + query = f""" + EasmAsset + | where TimeGeneratedValue > ago(24h) + | where AssetLastSeen > ago(30d) + | where AssetType == 'HOST' + | where Labels == '[]' + | where AssetName !startswith '*.' + | where AssetName endswith '.gc.ca' or AssetName endswith '.canada.ca' + | join kind=inner EasmHostAsset on AssetName + | where TimeGeneratedValue > ago(24h) + | where Cnames == '[]' + | summarize by AssetName + """ + try: + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return [asset["AssetName"] for asset in data] + except Exception as e: + logger.error(f"Failed to get unlabelled assets: {e}") + return [] diff --git a/azure-defender-easm/add-easm-assets-to-tracker/cloudbuild.yaml b/azure-defender-easm/add-easm-assets-to-tracker/cloudbuild.yaml new file mode 100644 index 0000000000..7992fe6a89 --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/cloudbuild.yaml @@ -0,0 +1,34 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + id: generate-image-name + entrypoint: "bash" + dir: azure-defender-easm/add-easm-assets-to-tracker + args: + - "-c" + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/azure-defender-easm/add-easm-assets-to-tracker:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: "gcr.io/cloud-builders/docker" + id: build + entrypoint: "bash" + dir: azure-defender-easm/add-easm-assets-to-tracker + args: + - "-c" + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: "gcr.io/cloud-builders/docker" + id: push-if-master + entrypoint: "bash" + dir: azure-defender-easm/add-easm-assets-to-tracker + args: + - "-c" + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi diff --git a/azure-defender-easm/add-easm-assets-to-tracker/docker-compose.yaml b/azure-defender-easm/add-easm-assets-to-tracker/docker-compose.yaml new file mode 100644 index 0000000000..e8f86e21bf --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + add-easm-assets-to-tracker: + image: northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/azure-defender-easm/add-easm-assets-to-tracker:dirty-${TAG} + build: ./ + env_file: + - .env + network_mode: "host" diff --git a/azure-defender-easm/add-easm-assets-to-tracker/requirements.txt b/azure-defender-easm/add-easm-assets-to-tracker/requirements.txt new file mode 100644 index 0000000000..429aaa8fa2 --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/requirements.txt @@ -0,0 +1,7 @@ +azure-kusto-data==4.3.1 +python-dotenv==1.0.0 +python-arango==8.3.1 +nats-py==2.6.0 +numpy==2.4.4 +pandas==3.0.2 +dnspython==2.6.1 \ No newline at end of file diff --git a/azure-defender-easm/add-easm-assets-to-tracker/service.py b/azure-defender-easm/add-easm-assets-to-tracker/service.py new file mode 100644 index 0000000000..e0fdfde1a5 --- /dev/null +++ b/azure-defender-easm/add-easm-assets-to-tracker/service.py @@ -0,0 +1,347 @@ +import logging +import os +import re +import json +from datetime import datetime, timezone +from arango import ArangoClient + +from dotenv import load_dotenv +import asyncio +import nats + +import dns.resolver +from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers +from dns.exception import Timeout + +load_dotenv() + +logging.basicConfig( + level=logging.INFO, format="[%(asctime)s :: %(name)s :: %(levelname)s] %(message)s" +) +logger = logging.getLogger() + +TIMEOUT = int(os.getenv("SCAN_TIMEOUT", "20")) + +from clients.kusto_client import ( + get_labelled_org_assets_from_org_key, + get_unlabelled_assets, +) + +DB_USER = os.getenv("DB_USER") +DB_PASS = os.getenv("DB_PASS") +DB_NAME = os.getenv("DB_NAME") +DB_URL = os.getenv("DB_URL") + +NATS_URL = os.getenv("NATS_URL") +UNCLAIMED_ID = os.getenv("UNCLAIMED_ID") +SERVICE_ACCOUNT_EMAIL = os.getenv("SERVICE_ACCOUNT_EMAIL") + +# Establish DB connection +arango_client = ArangoClient(hosts=DB_URL) +db = arango_client.db(DB_NAME, username=DB_USER, password=DB_PASS) + + +async def main(): + logger.info("Connecting to NATS") + nc = await nats.connect(NATS_URL) + logger.info("Successfully connected to NATS") + + js = nc.jetstream() + + async def publish(channel, msg): + await js.publish(channel, msg) + + # queries + def get_verified_orgs(): + query = """ + FOR org IN organizations + FILTER org.verified == true + RETURN { "key": org._key, "id": org._id } + """ + try: + cursor = db.aql.execute(query) + logger.info(f"Successfully fetched verified orgs") + return [domain for domain in cursor] + except Exception as e: + logger.error(f"Error occurred when fetching verified orgs: {e}") + return [] + + def get_org_domains(org_id): + query = f""" + FOR v, e IN 1..1 OUTBOUND @org_id claims + RETURN v.domain + """ + cursor = db.aql.execute(query, bind_vars={"org_id": org_id}) + logger.info(f"Successfully fetched domains for org: {org_id}") + return [domain for domain in cursor] + + def get_domain_exists(domain): + query = """ + FOR domain IN domains + FILTER domain.domain == @domain + RETURN domain + """ + bind_vars = {"domain": domain} + try: + cursor = db.aql.execute(query, bind_vars=bind_vars) + domains = [domain for domain in cursor] + return len(domains) > 0 + except Exception as e: + logger.error(f"Error occurred when checking if domain exists: {e}") + return None + + def extract_root_domains(subdomains): + root_domains_set = set() + for subdomain in subdomains: + parts = subdomain.split(".") + if len(parts) > 1: + root_domain = ".".join(parts[-3:]) + match = re.match(r"^([^.]+)\.(gc|canada)\.ca$", root_domain) + if match: + full_root_domain = match.group(0) + root_domains_set.add(full_root_domain) + + unique_root_domains = list(root_domains_set) + return unique_root_domains + + # insert functions + def create_domain(domain: str, txn_col): + insert_domain = { + "domain": domain.lower(), + "lastRan": None, + "status": { + "certificates": "info", + "ciphers": "info", + "curves": "info", + "dkim": "info", + "dmarc": "info", + "hsts": "info", + "https": "info", + "protocols": "info", + "spf": "info", + "ssl": "info", + }, + "archived": False, + "ignoreRua": False, + } + + try: + created_domain = txn_col.insert(insert_domain) + logger.info(f"Successfully created domain: {domain}") + return created_domain + except Exception as e: + logger.error(f"Error occurred when creating domain: {e}") + return None + + def create_claim(org_id, domain_id, domain_name, txn_col): + insert_claim = { + "_from": org_id, + "_to": domain_id, + "tags": ['new-nouveau'], + "assetState": "approved", + "firstSeen": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[ + :-3 + ] + + "Z", + } + + try: + created_claim = txn_col.insert(insert_claim) + logger.info(f"Successfully created claim for domain: {domain_name}") + return created_claim + except Exception as e: + logger.error(f"Error occurred when creating claim for {domain_name}", e) + return None + + def log_activity(domain, org_key, txn_col): + insert_activity = { + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[ + :-3 + ] + + "Z", + "initiatedBy": { + "id": "easm", + "userName": SERVICE_ACCOUNT_EMAIL, + "role": "service", + }, + "target": { + "resource": domain, + "updatedProperties": [ + { + "name": "tags", + "oldValue": [], + "newValue": ['new-nouveau'], + } + ], + "organization": {"id": org_key}, + "resourceType": "domain", + }, + "action": "add", + "reason": None, + } + + try: + created_log = txn_col.insert(insert_activity) + logger.info(f"Successfully logged activity for domain: {domain}") + return created_log + except Exception as e: + logger.error(f"Error occurred when logging activity for {domain}: {e}") + return None + + # main logic + async def add_discovered_domain(domains, org_id): + for domain in domains: + # check if domain exists in system + domain_exists = get_domain_exists(domain) + if domain_exists is None: + logger.error(f"Error occurred when checking if domain exists: {e}") + continue + # if domain exists, skip + elif domain_exists: + logger.info(f"Domain: {domain} already exists in system") + continue + + # check for NXDOMAIN + resolver = dns.resolver.Resolver() + resolver.timeout = TIMEOUT + resolver.lifetime = TIMEOUT * 2 + try: + resolver.resolve(qname=domain, rdtype=dns.rdatatype.A) + except (NoAnswer, NXDOMAIN, NoNameservers, Timeout): + logger.info(f"Domain: {domain} does not have a valid DNS entry") + continue + except Exception as e: + logger.error( + f"Could not confirm if domain: {domain} has a valid DNS entry" + ) + continue + + # setup transaction + txn_db = db.begin_transaction( + write=[ + db.collection("domains").name, + db.collection("claims").name, + db.collection("auditLogs").name, + ], + ) + txn_col_domains = txn_db.collection("domains") + txn_col_claims = txn_db.collection("claims") + txn_col_audit_logs = txn_db.collection("auditLogs") + + # create domain + created_domain = create_domain(domain=domain, txn_col=txn_col_domains) + if created_domain is None: + # abort transaction + txn_db.abort_transaction() + continue + # add domain to org + created_claim = create_claim( + org_id=org_id, + domain_id=created_domain["_id"], + domain_name=domain, + txn_col=txn_col_claims, + ) + if created_claim is None: + # abort transaction + txn_db.abort_transaction() + continue + # add activity logging + org_key = org_id.split("/")[-1] + created_log = log_activity( + domain=domain, org_key=org_key, txn_col=txn_col_audit_logs + ) + if created_log is None: + # abort transaction + txn_db.abort_transaction() + continue + + # commit transaction + try: + txn_db.commit_transaction() + logger.info(f"Successfully committed transaction for domain: {domain}") + + # publish domain to NATS + try: + await publish( + "scans.requests", + json.dumps( + { + "domain": domain, + "domain_key": created_domain["_key"], + } + ).encode(), + ) + logger.info(f"Published domain: {domain} to NATS") + except Exception as e: + logger.error(f"Failed to publish domain: {domain} to NATS: {e}") + + except Exception as e: + logger.error(f"Failed to commit transaction for domain: {domain}: {e}") + # abort transaction + txn_db.abort_transaction() + continue + + # Get all unlabelled assets once + try: + unlabelled_assets = set(get_unlabelled_assets()) + except Exception as e: + logger.error(f"Error fetching unlabelled assets: {e}") + unlabelled_assets = set() + + org_domain_roots = {} + + verified_orgs = get_verified_orgs() + for org in verified_orgs: + org_key = org["key"] + org_id = org["id"] + + try: + domains = get_org_domains(org_id) + except Exception as e: + logger.error( + f"Error when attempting to fetch domains for org {org_key}: {e}" + ) + continue + + # Extract root domains + try: + unique_roots = extract_root_domains(domains) + org_domain_roots[org_key] = unique_roots + except Exception as e: + logger.error(e) + unique_roots = [] + + try: + labelled_assets = get_labelled_org_assets_from_org_key(org_key) + new_domains = list(set(labelled_assets) - set(domains)) + + for asset in list(unlabelled_assets): + asset_root = extract_root_domains([asset]) + if asset_root and asset_root[0] in unique_roots: + new_domains.append(asset) + unlabelled_assets.discard(asset) + + await add_discovered_domain(new_domains, org_id) + except Exception as e: + logger.error( + f"Error when attempting to add new assets to org {org_key}: {e}" + ) + continue + + # After all orgs, add remaining unlabelled assets to unclaimed org + try: + unclaimed_domains = get_org_domains(UNCLAIMED_ID) + new_domains = list(unlabelled_assets - set(unclaimed_domains)) + await add_discovered_domain(new_domains, UNCLAIMED_ID) + except Exception as e: + logger.error(f"Error when attempting to add new assets to unclaimed org: {e}") + + logger.info("Closing NATS connection") + await nc.close() + logger.info("Successfully closed connection") + + +if __name__ == "__main__": + logger.info("Starting EASM-to-Tracker sync") + asyncio.run(main()) + logger.info(f"EASM-to-Tracker sync shutting down...") diff --git a/azure-defender-easm/import-easm-additional-findings/.dockerignore b/azure-defender-easm/import-easm-additional-findings/.dockerignore new file mode 100644 index 0000000000..c2440b7a4f --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +tests +*.yaml +**/*.env diff --git a/azure-defender-easm/import-easm-additional-findings/.env.example b/azure-defender-easm/import-easm-additional-findings/.env.example new file mode 100644 index 0000000000..bed1213e87 --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/.env.example @@ -0,0 +1,13 @@ +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= +CVE_LIST= + +KUSTO_CLUSTER= +KUSTO_DATABASE= +REGION= + +DB_USER= +DB_PASS= +DB_URL= +DB_NAME= \ No newline at end of file diff --git a/azure-defender-easm/import-easm-additional-findings/Dockerfile b/azure-defender-easm/import-easm-additional-findings/Dockerfile new file mode 100644 index 0000000000..1eaf2c1556 --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/Dockerfile @@ -0,0 +1,41 @@ +FROM python:3.14.3-alpine AS python-builder + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /working/install + +RUN apk add --no-cache \ + python3 \ + py3-pip \ + py3-setuptools \ + py3-wheel \ + build-base \ + python3-dev + +COPY requirements.txt /requirements.txt +# Install python requirements to /working/install directory for cleaner copy +RUN pip3 install --prefix=/working/install -r /requirements.txt + +#=============================================================================================== +#=============================================================================================== + +FROM python:3.14.3-alpine + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /import-easm-additional-findings + +# Copy installed python modules +COPY --from=python-builder /working/install/lib /usr/local/lib + +COPY service.py ./ +COPY clients ./clients + +RUN adduser -D defender +USER defender + +CMD ["python3", "service.py"] diff --git a/azure-defender-easm/import-easm-additional-findings/README.md b/azure-defender-easm/import-easm-additional-findings/README.md new file mode 100644 index 0000000000..b7512a8c22 --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/README.md @@ -0,0 +1,5 @@ +# Azure EASM + +This is an optional service that links Tracker with the [Microsoft Defender External Attack Surface Management (EASM)](https://learn.microsoft.com/en-us/azure/external-attack-surface-management/). This allows users and service admins to gain new insight into their assets, including asset discovery and CVE detection. + +## TODO diff --git a/services/auto-scan/__init__.py b/azure-defender-easm/import-easm-additional-findings/clients/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from services/auto-scan/__init__.py rename to azure-defender-easm/import-easm-additional-findings/clients/__init__.py diff --git a/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py b/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py new file mode 100644 index 0000000000..d7188964dc --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/clients/kusto_client.py @@ -0,0 +1,218 @@ +from azure.kusto.data import KustoClient, KustoConnectionStringBuilder +from azure.kusto.data.helpers import dataframe_from_result_table +from datetime import datetime, date, timedelta +import logging +import os +import requests +import time +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +KUSTO_CLUSTER = os.getenv("KUSTO_CLUSTER") +REGION = os.getenv("REGION") +KUSTO_DATABASE = os.getenv("KUSTO_DATABASE") +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +TENANT_ID = os.getenv("TENANT_ID") +CVE_LIST = os.getenv("CVE_LIST") + +KCSB_DATA = KustoConnectionStringBuilder.with_aad_application_key_authentication( + f"https://{KUSTO_CLUSTER}.{REGION}.kusto.windows.net", + CLIENT_ID, + CLIENT_SECRET, + TENANT_ID, +) +KUSTO_CLIENT = KustoClient(KCSB_DATA) + + +def filter_recent_data(data_list, last_seen_key, start_date): + try: + return [ + x + for x in data_list + if ( + # There are some strange entries such as port '-1' which don't have a last seen date. Skip these. + x[last_seen_key] + and datetime.strptime(x[last_seen_key].split("T")[0], "%Y-%m-%d").date() + >= start_date + ) + ] + except AttributeError as e: + logger.error( + f"Problem occurred filtering list to recent entries. Returning full list... Error: {e}" + ) + return data_list + + +def get_web_components_by_asset(asset, fetched_cves): + query = f""" + declare query_parameters(asset_name:string = '{asset}'); + EasmAssetWebComponent + | where AssetName == asset_name + | where TimeGeneratedValue > ago(30d) + | where WebComponentLastSeen > ago(30d) + | summarize max_time = max(TimeGeneratedValue) by AssetName + | join kind=inner ( + EasmAssetWebComponent + | where AssetName == asset_name + | where TimeGeneratedValue > ago(30d) + | where WebComponentLastSeen > ago(30d) + ) on $left.max_time == $right.TimeGeneratedValue + | project WebComponentName, WebComponentCategory, WebComponentVersion, WebComponentFirstSeen, WebComponentLastSeen, WebComponentCves, WebComponentPorts + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + + for wc in data: + # format datetime to isoformat + wc["WebComponentFirstSeen"] = wc["WebComponentFirstSeen"].isoformat() + wc["WebComponentLastSeen"] = wc["WebComponentLastSeen"].isoformat() + + # filter cves to only top 25 + top25 = CVE_LIST.split(",") + wc["WebComponentCves"] = [ + cve for cve in wc["WebComponentCves"] if cve["Cve"] in top25 + ] + + component_versions = wc["WebComponentVersion"].split(".", 2) + # Assign confidence levels to each CVE + for cve in wc["WebComponentCves"]: + cve["ConfidenceLevel"] = "unknown" + # if detected version includes patch, high confidence + if len(component_versions) == 3: + cve["ConfidenceLevel"] = "high" + else: + # fetch affected versions of CVE + affected_versions = fetch_cve_affected_versions( + cve["Cve"], wc["WebComponentName"], fetched_cves + ) + for cpe in affected_versions: + try: + start, end = get_version_range(cpe).values() + # compare minor and major version nums + if len(component_versions) == 2: + major = int(component_versions[0]) + minor = int(component_versions[1]) + if start is not None: + if major < int(start.split(".")[0]): + continue + if major > int(end.split(".")[0]): + continue + + if minor < int(end.split(".")[1]): + cve["ConfidenceLevel"] = "high" + elif minor == int(end.split(".")[1]): + cve["ConfidenceLevel"] = "medium" + elif len(component_versions) == 1: + major = int(component_versions[0]) + if start is not None: + if major < int(start.split(".")[0]): + continue + if major < int(end.split(".")[0]): + cve["ConfidenceLevel"] = "high" + elif major == int(end.split(".")[0]): + cve["ConfidenceLevel"] = "low" + except Exception as e: + logger.error( + f"Encountered problem while assigning confidence level for {cve['Cve']} on {asset}: {e}" + ) + continue + + return data + + +def fetch_cve_affected_versions(cve, comp_name, fetched_cves): + try: + return fetched_cves[cve] + except KeyError: + logger.info(f"Data on {cve} has not been fetched yet. Attempting to fetch it.") + + res = {"status_code": 0} + attempts = 1 + while attempts <= 3: + try: + res = requests.get( + f"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve}" + ) + # Check if the response is successful + if res.status_code == 200: + data = res.json() + found = [] + configurations = data["vulnerabilities"][0]["cve"]["configurations"] + for item in configurations: + for node in item["nodes"]: + for cpe in node["cpeMatch"]: + if cpe["criteria"].find(comp_name.lower()) != -1: + found.append(cpe) + fetched_cves[cve] = found + return found + else: + time.sleep(60) + attempts += 1 + except Exception as e: + logger.error(f"Encountered problem while fetching cves for {cve}: {e}") + return None + + # Unable to fetch CVE data + logger.error(f"Unable to fetch data for CVE: {cve}") + fetched_cves[cve] = None + return None + + +def get_version_range(affected_versions): + versions = {"start": None, "end": None} + + for key in ["versionStartExcluding", "versionStartIncluding"]: + if affected_versions.get(key): + versions["start"] = affected_versions[key] + break + + for key in ["versionEndExcluding", "versionEndIncluding"]: + if affected_versions.get(key): + versions["end"] = affected_versions[key] + break + + return versions + + +def get_additional_findings_by_asset(asset): + thirty_days_ago = date.today() - timedelta(days=30) + query = f""" + declare query_parameters(asset_name:string = '{asset}'); + EasmHostAsset + | where AssetName == asset_name + | where TimeGeneratedValue > ago(30d) + | order by TimeGeneratedValue desc + | limit 1 + | project Locations, Ports, Headers + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + result_list = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + + findings = { + "Locations": [], + "Ports": [], + "Headers": [], + } + + if len(result_list) == 0: + return findings + + data = result_list[0] + + findings["Ports"] = filter_recent_data( + data["Ports"], "PortStateLastSeen", thirty_days_ago + ) + findings["Locations"] = filter_recent_data( + data["Locations"], "LastSeen", thirty_days_ago + ) + + findings["Headers"] = data["Headers"] + + return findings diff --git a/azure-defender-easm/import-easm-additional-findings/cloudbuild.yaml b/azure-defender-easm/import-easm-additional-findings/cloudbuild.yaml new file mode 100644 index 0000000000..7c663bb500 --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/cloudbuild.yaml @@ -0,0 +1,34 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + id: generate-image-name + entrypoint: "bash" + dir: azure-defender-easm/import-easm-additional-findings + args: + - "-c" + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/azure-defender-easm/import-easm-additional-findings:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: "gcr.io/cloud-builders/docker" + id: build + entrypoint: "bash" + dir: azure-defender-easm/import-easm-additional-findings + args: + - "-c" + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: "gcr.io/cloud-builders/docker" + id: push-if-master + entrypoint: "bash" + dir: azure-defender-easm/import-easm-additional-findings + args: + - "-c" + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi diff --git a/azure-defender-easm/import-easm-additional-findings/docker-compose.yaml b/azure-defender-easm/import-easm-additional-findings/docker-compose.yaml new file mode 100644 index 0000000000..ada7c87b1d --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + import-easm-additional-findings: + image: northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/import-easm-additional-findings:dirty-${TAG} + build: ./ + env_file: + - .env + network_mode: "host" diff --git a/azure-defender-easm/import-easm-additional-findings/requirements.txt b/azure-defender-easm/import-easm-additional-findings/requirements.txt new file mode 100644 index 0000000000..078308b78f --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/requirements.txt @@ -0,0 +1,5 @@ +azure-kusto-data==4.3.1 +python-dotenv==1.1.0 +python-arango==8.3.1 +numpy==2.4.4 +pandas==3.0.2 \ No newline at end of file diff --git a/azure-defender-easm/import-easm-additional-findings/service.py b/azure-defender-easm/import-easm-additional-findings/service.py new file mode 100644 index 0000000000..b6ffb4d2dd --- /dev/null +++ b/azure-defender-easm/import-easm-additional-findings/service.py @@ -0,0 +1,124 @@ +import json +from datetime import datetime +from arango import ArangoClient +import os +from clients.kusto_client import ( + get_web_components_by_asset, + get_additional_findings_by_asset, +) +from dotenv import load_dotenv +import logging + +load_dotenv() + +DB_USER = os.getenv("DB_USER") +DB_PASS = os.getenv("DB_PASS") +DB_NAME = os.getenv("DB_NAME") +DB_URL = os.getenv("DB_URL") + +logging.basicConfig( + level=logging.INFO, format="[%(asctime)s :: %(name)s :: %(levelname)s] %(message)s" +) +logger = logging.getLogger() + +# Establish DB connection +arango_client = ArangoClient(hosts=DB_URL) +db = arango_client.db(DB_NAME, username=DB_USER, password=DB_PASS) + + +# fetch all domains from the DB +def get_all_domains(): + query = """ + FOR domain IN domains + FILTER domain.archived != True + FILTER domain.rcode != "NXDOMAIN" + RETURN { "domain": domain.domain, "id": domain._id, "key": domain._key, "ignoredCves": domain.ignoredCves || [] } + """ + cursor = db.aql.execute(query) + return [domain for domain in cursor] + + +def upsert_finding(finding): + query = f""" + UPSERT {{ domain: "{finding["domain"]}" }} + INSERT {finding} + UPDATE {finding} + IN additionalFindings + """ + cursor = db.aql.execute(query) + return [domain for domain in cursor] + + +def remove_none_val_in_dict(dict): + new_dict = {} + for k, v in dict: + if v is None: + v = "" + new_dict[k] = v + return new_dict + + +def update_domain_cve_detected(domain, web_components): + cve_detected = False + ignored_cves = domain["ignoredCves"] if domain["ignoredCves"] else [] + for wc in web_components: + non_ignored_cves = [ + cve + for cve in wc["WebComponentCves"] + if cve["Cve"] not in ignored_cves and cve["ConfidenceLevel"] == "high" + ] + if len(non_ignored_cves) > 0: + cve_detected = True + break + query = f""" + UPDATE {{ _key: "{domain["key"]}", cveDetected: {cve_detected} }} IN domains + """ + cursor = db.aql.execute(query) + return [domain for domain in cursor] + + +def main(): + fetched_cves = {} + try: + domains = get_all_domains() + except Exception as e: + logger.error(f"Failed to fetch domains: {e}. Exiting service...") + return + logger.info(f"Successfully fetched {len(domains)} domains") + + for domain in domains: + logger.info(f"Processing domain {domain['domain']}") + try: + web_components = get_web_components_by_asset(domain["domain"], fetched_cves) + additional_findings = get_additional_findings_by_asset(domain["domain"]) + + insert_str = json.dumps( + { + "domain": domain["id"], + "timestamp": datetime.today().isoformat(), + "webComponents": web_components, + "locations": additional_findings["Locations"], + "ports": additional_findings["Ports"], + "headers": additional_findings["Headers"], + } + ) + insert_obj = json.loads( + insert_str, object_pairs_hook=remove_none_val_in_dict + ) + + # insert the findings into the DB + logger.info(f"Upserting additional findings for domain {domain['domain']}") + upsert_finding(insert_obj) + update_domain_cve_detected(domain, web_components) + except Exception as e: + logger.error(f"Failed to process domain {domain['domain']}: {e}") + continue + logger.info( + f"Successfully upserted additional findings for domain {domain['domain']}" + ) + + +if __name__ == "__main__": + logger.info("EASM additional findings import service started") + main() + logger.info(f"EASM additional findings import service shutting down...") diff --git a/azure-defender-easm/label-known-easm-assets/.dockerignore b/azure-defender-easm/label-known-easm-assets/.dockerignore new file mode 100644 index 0000000000..c2440b7a4f --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/.dockerignore @@ -0,0 +1,4 @@ +Dockerfile +tests +*.yaml +**/*.env diff --git a/azure-defender-easm/label-known-easm-assets/.env.example b/azure-defender-easm/label-known-easm-assets/.env.example new file mode 100644 index 0000000000..e60fb6b530 --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/.env.example @@ -0,0 +1,16 @@ +SUBSCRIPTION_ID= +WORKSPACE_NAME= +RESOURCE_GROUP= +REGION= + +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= + +KUSTO_CLUSTER= +KUSTO_DATABASE= + +DB_USER= +DB_PASS= +DB_URL= +DB_NAME= \ No newline at end of file diff --git a/azure-defender-easm/label-known-easm-assets/Dockerfile b/azure-defender-easm/label-known-easm-assets/Dockerfile new file mode 100644 index 0000000000..a3fa7adf2b --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/Dockerfile @@ -0,0 +1,41 @@ +FROM python:3.14.3-alpine AS python-builder + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /working/install + +RUN apk add --no-cache \ + python3 \ + py3-pip \ + py3-setuptools \ + py3-wheel \ + build-base \ + python3-dev + +COPY requirements.txt /requirements.txt +# Install python requirements to /working/install directory for cleaner copy +RUN pip3 install --prefix=/working/install -r /requirements.txt + +#=============================================================================================== +#=============================================================================================== + +FROM python:3.14.3-alpine + +# Copy local code to the container image. +ENV PYTHONUNBUFFERED 1 +ENV PYTHONWARNINGS ignore + +WORKDIR /label-known-easm-assets + +# Copy installed python modules +COPY --from=python-builder /working/install/lib /usr/local/lib + +COPY service.py ./ +COPY clients ./clients + +RUN adduser -D defender +USER defender + +CMD ["python3", "service.py"] diff --git a/azure-defender-easm/label-known-easm-assets/README.md b/azure-defender-easm/label-known-easm-assets/README.md new file mode 100644 index 0000000000..b41c60e8aa --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/README.md @@ -0,0 +1,12 @@ +# Azure EASM + +This is an optional service that links Tracker with the [Microsoft Defender External Attack Surface Management (EASM)](https://learn.microsoft.com/en-us/azure/external-attack-surface-management/). This allows users and service admins to gain new insight into their assets, including asset discovery and CVE detection. + +## Running it + +``` +pipenv install +pipenv run service +``` + +## TODO diff --git a/services/results/__init__.py b/azure-defender-easm/label-known-easm-assets/clients/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from services/results/__init__.py rename to azure-defender-easm/label-known-easm-assets/clients/__init__.py diff --git a/azure-defender-easm/label-known-easm-assets/clients/easm_client.py b/azure-defender-easm/label-known-easm-assets/clients/easm_client.py new file mode 100644 index 0000000000..c4ce43c7c7 --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/clients/easm_client.py @@ -0,0 +1,33 @@ +import os +import logging +from azure.identity import ClientSecretCredential +from azure.defender.easm import EasmClient +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +SUB_ID = os.getenv("SUBSCRIPTION_ID") +WORKSPACE_NAME = os.getenv("WORKSPACE_NAME") +RESOURCE_GROUP = os.getenv("RESOURCE_GROUP") +REGION = os.getenv("REGION") +ENDPOINT = f"{REGION}.easm.defender.microsoft.com" + +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +TENANT_ID = os.getenv("TENANT_ID") +CREDENTIAL = ClientSecretCredential(TENANT_ID, CLIENT_ID, CLIENT_SECRET) + +EASM_CLIENT = EasmClient(ENDPOINT, RESOURCE_GROUP, SUB_ID, WORKSPACE_NAME, CREDENTIAL) + + +def label_assets(assets, label): + for asset in assets: + update_request = {"labels": {f"{label}": True}} + asset_filter = f"uuid = {asset['AssetUuid']}" + try: + EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + logger.info(f"{asset['AssetName']} labeled with {label}") + except Exception as e: + logger.error(f"Failed to label {asset['AssetName']}: {e}") + continue diff --git a/azure-defender-easm/label-known-easm-assets/clients/kusto_client.py b/azure-defender-easm/label-known-easm-assets/clients/kusto_client.py new file mode 100644 index 0000000000..fe11fe0e53 --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/clients/kusto_client.py @@ -0,0 +1,68 @@ +from azure.kusto.data import KustoClient, KustoConnectionStringBuilder +from azure.kusto.data.helpers import dataframe_from_result_table + +import logging +import os +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +KUSTO_CLUSTER = os.getenv("KUSTO_CLUSTER") +REGION = os.getenv("REGION") +KUSTO_DATABASE = os.getenv("KUSTO_DATABASE") +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +TENANT_ID = os.getenv("TENANT_ID") + +KCSB_DATA = KustoConnectionStringBuilder.with_aad_application_key_authentication( + f"https://{KUSTO_CLUSTER}.{REGION}.kusto.windows.net", + CLIENT_ID, + CLIENT_SECRET, + TENANT_ID, +) +KUSTO_CLIENT = KustoClient(KCSB_DATA) + + +def get_unlabelled_org_assets_from_root(root): + query = f""" + declare query_parameters(domainRoot:string = '{root}'); + EasmAsset + | where TimeGeneratedValue > ago(1d) + | where AssetType == 'HOST' + | where AssetName == domainRoot or AssetName endswith strcat('.', domainRoot) + | where Labels == '[]' + | project AssetName, AssetUuid, Labels + | order by AssetName asc + """ + try: + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + except Exception as e: + logger.error(f"Failed to get unlabelled assets from roots: {e}") + return [] + + +def get_unlabelled_org_assets_from_domains(domains): + query = f""" + declare query_parameters(domains:dynamic = dynamic({domains})); + EasmAsset + | where TimeGeneratedValue > ago(1d) + | where AssetType == 'HOST' + | where AssetName in (domains) + | where Labels == '[]' + | project AssetName, AssetUuid, Labels + | order by AssetName asc + """ + try: + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + except Exception as e: + logger.error(f"Failed to get unlabelled assets: {e}") + return [] diff --git a/azure-defender-easm/label-known-easm-assets/cloudbuild.yaml b/azure-defender-easm/label-known-easm-assets/cloudbuild.yaml new file mode 100644 index 0000000000..25c39b161f --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/cloudbuild.yaml @@ -0,0 +1,34 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + id: generate-image-name + entrypoint: "bash" + dir: azure-defender-easm/label-known-easm-assets + args: + - "-c" + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/azure-defender-easm/label-known-easm-assets:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: "gcr.io/cloud-builders/docker" + id: build + entrypoint: "bash" + dir: azure-defender-easm/label-known-easm-assets + args: + - "-c" + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: "gcr.io/cloud-builders/docker" + id: push-if-master + entrypoint: "bash" + dir: azure-defender-easm/label-known-easm-assets + args: + - "-c" + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi diff --git a/azure-defender-easm/label-known-easm-assets/requirements.txt b/azure-defender-easm/label-known-easm-assets/requirements.txt new file mode 100644 index 0000000000..c4dd6060cc --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/requirements.txt @@ -0,0 +1,7 @@ +azure-identity==1.16.1 +azure-defender-easm==1.0.0b1 +azure-kusto-data==4.3.1 +python-dotenv==1.1.0 +python-arango==8.3.1 +numpy==2.4.4 +pandas==3.0.2 \ No newline at end of file diff --git a/azure-defender-easm/label-known-easm-assets/service.py b/azure-defender-easm/label-known-easm-assets/service.py new file mode 100644 index 0000000000..07d9547f57 --- /dev/null +++ b/azure-defender-easm/label-known-easm-assets/service.py @@ -0,0 +1,125 @@ +import logging +import re +from arango import ArangoClient +import os + +from dotenv import load_dotenv + +load_dotenv() + +logging.basicConfig( + level=logging.INFO, format="[%(asctime)s :: %(name)s :: %(levelname)s] %(message)s" +) +logger = logging.getLogger() + +from clients.kusto_client import ( + get_unlabelled_org_assets_from_root, + get_unlabelled_org_assets_from_domains, +) +from clients.easm_client import label_assets + +DB_USER = os.getenv("DB_USER") +DB_PASS = os.getenv("DB_PASS") +DB_NAME = os.getenv("DB_NAME") +DB_URL = os.getenv("DB_URL") + +UNCLAIMED_ID = os.getenv("UNCLAIMED_ID") + +# Establish DB connection +arango_client = ArangoClient(hosts=DB_URL) +db = arango_client.db(DB_NAME, username=DB_USER, password=DB_PASS) + + +def get_verified_orgs(): + query = """ + FOR org IN organizations + FILTER org.verified == true + FILTER org._key != @unclaimed_id + SORT org.en.name ASC + RETURN { "key": org._key, "id": org._id } + """ + cursor = db.aql.execute(query, bind_vars={"unclaimed_id": UNCLAIMED_ID}) + return [org for org in cursor] + + +def get_org_domains(org_id): + query = f""" + FOR v, e IN 1..1 OUTBOUND @org_id claims + FILTER v.archived != true + FILTER v.rcode != "NXDOMAIN" + RETURN v.domain + """ + cursor = db.aql.execute(query, bind_vars={"org_id": org_id}) + return [domain for domain in cursor] + + +def extract_root_domains(subdomains): + root_domains_set = set() + for subdomain in subdomains: + parts = subdomain.split(".") + if len(parts) > 1: + root_domain = ".".join(parts[-3:]) + match = re.match(r"^([^.]+)\.(gc|canada)\.ca$", root_domain) + if match: + full_root_domain = match.group(0) + root_domains_set.add(full_root_domain) + + unique_root_domains = list(root_domains_set) + return unique_root_domains + + +def update_asset_labels(): + # Get verified org ids + try: + logger.info("Getting verified orgs") + verified_orgs = get_verified_orgs() + logger.info(f"Found {len(verified_orgs)} verified orgs") + except Exception as e: + logger.error(e) + return + for org in verified_orgs: + # Get org domains + try: + logger.info(f"Getting domains for org {org['key']}") + org_domains = get_org_domains(org["id"]) + logger.info(f"Found {len(org_domains)} domains") + except Exception as e: + logger.error(e) + continue + + # label known assets first + try: + logger.info(f"Labeling known assets for org {org['key']}") + known_org_assets = get_unlabelled_org_assets_from_domains(org_domains) + logger.info( + "Found " + str(len(known_org_assets)) + " known unlabelled assets" + ) + label_assets(assets=known_org_assets, label=org["key"]) + except Exception as e: + logger.error(e) + continue + + # Extract root domains + try: + unique_roots = extract_root_domains(org_domains) + except Exception as e: + logger.error(e) + + for root in unique_roots: + try: + logger.info(f"Root domain: {root}") + # get unlabelled assets from roots + unlabelled_org_assets = get_unlabelled_org_assets_from_root(root) + logger.info( + "Found " + str(len(unlabelled_org_assets)) + " unlabelled assets" + ) + label_assets(assets=unlabelled_org_assets, label=org["key"]) + except Exception as e: + logger.error(e) + continue + + +if __name__ == "__main__": + logger.info("EASM label service started") + update_asset_labels() + logger.info(f"EASM label service shutting down...") diff --git a/azure-defender-easm/scripts/.env.example b/azure-defender-easm/scripts/.env.example new file mode 100644 index 0000000000..e60fb6b530 --- /dev/null +++ b/azure-defender-easm/scripts/.env.example @@ -0,0 +1,16 @@ +SUBSCRIPTION_ID= +WORKSPACE_NAME= +RESOURCE_GROUP= +REGION= + +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= + +KUSTO_CLUSTER= +KUSTO_DATABASE= + +DB_USER= +DB_PASS= +DB_URL= +DB_NAME= \ No newline at end of file diff --git a/azure-defender-easm/scripts/ddos-protection-detection.py b/azure-defender-easm/scripts/ddos-protection-detection.py new file mode 100644 index 0000000000..c030a173ab --- /dev/null +++ b/azure-defender-easm/scripts/ddos-protection-detection.py @@ -0,0 +1,40 @@ +import csv +import time +from kusto_queries import ( + get_hosts_with_ddos_protection, + host_has_ddos_protection, +) + + +def export_to_csv(data, filename): + with open(filename, "w") as csvfile: + fieldnames = ["AssetName", "HasDDOSProtection"] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + writer.writeheader() + for row in data: + writer.writerow(row) + + +def ddos_protection_detection_service(): + protected_assets = get_hosts_with_ddos_protection() + print(f"Found {len(protected_assets)} host assets") + + export_arr = [] + for host in protected_assets: + ddos_protection = host_has_ddos_protection(host["AssetName"]) + export_arr.append( + { + "AssetName": host["AssetName"], + "HasDDOSProtection": ddos_protection, + } + ) + + current_time = time.strftime("%Y-%m-%d") + export_to_csv(export_arr, f"ddos_protection_{current_time}.csv") + + +if __name__ == "__main__": + print("DDOS Protection Detection Service started") + ddos_protection_detection_service() + print("DDOS Protection Detection Service done") diff --git a/azure-defender-easm/scripts/easm_client.py b/azure-defender-easm/scripts/easm_client.py new file mode 100644 index 0000000000..d686664d51 --- /dev/null +++ b/azure-defender-easm/scripts/easm_client.py @@ -0,0 +1,21 @@ +import os +from azure.identity import ClientSecretCredential +from azure.defender.easm import EasmClient +from dotenv import load_dotenv + +load_dotenv() + +sub_id = os.getenv("SUBSCRIPTION_ID") +workspace_name = os.getenv("WORKSPACE_NAME") +resource_group = os.getenv("RESOURCE_GROUP") +region = os.getenv("REGION") +endpoint = f"{region}.easm.defender.microsoft.com" + +client_id = os.getenv("CLIENT_ID") +client_secret = os.getenv("CLIENT_SECRET") +tenant_id = os.getenv("TENANT_ID") +credential = ClientSecretCredential( + client_id=client_id, client_secret=client_secret, tenant_id=tenant_id +) + +EASM_CLIENT = EasmClient(endpoint, resource_group, sub_id, workspace_name, credential) diff --git a/azure-defender-easm/scripts/easm_discover_assets.py b/azure-defender-easm/scripts/easm_discover_assets.py new file mode 100644 index 0000000000..6511a37db0 --- /dev/null +++ b/azure-defender-easm/scripts/easm_discover_assets.py @@ -0,0 +1,37 @@ +from easm_client import EASM_CLIENT + + +def run_disco_group(group_name): + org_disco_group = get_disco_group(group_name) + if org_disco_group: + EASM_CLIENT.discovery_groups.run(group_name) + + +def list_disco_group_runs(group_name): + org_disco_group = get_disco_group(group_name) + if org_disco_group: + return EASM_CLIENT.discovery_groups.list_runs(group_name) + + +def get_disco_group(group_name): + disco_group = EASM_CLIENT.discovery_groups.get(group_name) + return disco_group + + +def create_disco_group(name, assets, frequency=0): + request = { + "description": "Discovery group made for discovering assets for " + name, + "seeds": assets, + "frequencyMilliseconds": frequency, + } + response = EASM_CLIENT.discovery_groups.put(name, request) + return response + + +def delete_disco_group(group_name): + EASM_CLIENT.discovery_groups.delete(group_name) + + +def list_disco_groups(): + for dg in EASM_CLIENT.discovery_groups.list(): + print(f'{dg["id"]}: {dg["name"]}') diff --git a/azure-defender-easm/scripts/easm_get_asset.py b/azure-defender-easm/scripts/easm_get_asset.py new file mode 100644 index 0000000000..022401c3e5 --- /dev/null +++ b/azure-defender-easm/scripts/easm_get_asset.py @@ -0,0 +1,139 @@ +from easm_client import EASM_CLIENT + + +UNNECESSARY_OPEN_PORTS = { + "FTP": [20, 21], + "SSH": [22], + "Telnet": [23], + "SMTP": [25, 587], + "DNS": [53], + "NetBIOS": [137, 139], + "SMB": [445], + "Ports": [1433, 1434, 3306, 465, 55443], + "Remote desktop": [3389], + "MQTT": [1883, 8883], + "AMQP": [5672, 5671], +} + +UNNECESSARY_EXPOSED_HEADERS = [ + "x-aspnet-version", + "server", + "x-powered-by", +] + + +def get_all_asset_cves(asset): + web_components = asset["asset"]["webComponents"] + detected_cves = { + "LOW": [], + "MEDIUM": [], + "HIGH": [], + "CRITICAL": [], + } + for web_component in web_components: + for cve in web_component["cve"]: + severity = cve["cvss3Summary"]["baseSeverity"] + detected_cves[severity].append(cve["name"]) + + for k, v in detected_cves.items(): + v = list(set(v)) + v.sort(reverse=True) + print(f"{k}: {v}") + + return detected_cves + + +def get_web_components(asset): + for wc in asset["asset"]["webComponents"]: + print(wc["type"] + ":", wc["name"]) + return asset["asset"]["webComponents"] + + +def get_cookies(asset): + return asset["asset"]["cookies"] + + +def get_asset(asset_id): + return EASM_CLIENT.assets.get(asset_id=asset_id) + + +def get_vulnerable_web_components(asset): + vulnerable_web_components = [] + web_components = get_web_components(asset) + for wc in web_components: + if len(wc["cve"]) > 0: + print(wc["type"] + ":", wc["name"]) + vulnerable_web_components.append(wc) + return vulnerable_web_components + + +def get_all_unique_cves(vuln_components): + all_cves = [] + for wc in vuln_components: + for cve in wc["cve"]: + all_cves.append(cve["name"]) + return list(set(all_cves)) + + +def get_open_ports(asset): + services = asset["asset"]["services"] + open_ports = [] + for service in services: + open_ports.append(service["port"]) + return open_ports + + +def get_unnecessary_exposed_headers(asset): + headers = asset["asset"]["headers"] + exposed_headers = [] + for header in headers: + if header["headerName"] == "x-frame-options" and header["headerValue"] not in [ + "sameorigin", + "deny", + ]: + print(header["headerName"] + ":", header["headerValue"]) + exposed_headers.append(header["headerName"]) + elif header["headerName"] in UNNECESSARY_EXPOSED_HEADERS: + print(header["headerName"]) + exposed_headers.append(header["headerName"]) + return exposed_headers + + +def get_unnecessary_open_ports(asset): + open_ports = get_open_ports(asset) + unnecessary_open_ports = [] + for port in open_ports: + for service, ports in UNNECESSARY_OPEN_PORTS.items(): + if port in ports: + print(service + ":", port) + unnecessary_open_ports.append(port) + return { + "open_ports": open_ports, + "unnecessary_open_ports": unnecessary_open_ports, + } + + +def get_whois(asset): + return asset["asset"]["domainAsset"] + + +def get_web_component_types(asset): + web_components = get_web_components(asset) + types = [] + for wc in web_components: + types.append(wc["type"]) + return list(set(types)) + + +def three_one_two(asset): + types = get_web_component_types(asset) + if "DDOS Protection" in types: + return True + return False + + +def three_one_three(asset): + types = get_web_component_types(asset) + if "CDN" in types: + return True + return False diff --git a/azure-defender-easm/scripts/easm_update_asset.py b/azure-defender-easm/scripts/easm_update_asset.py new file mode 100644 index 0000000000..4a0a6a0903 --- /dev/null +++ b/azure-defender-easm/scripts/easm_update_asset.py @@ -0,0 +1,39 @@ +from easm_client import EASM_CLIENT + + +def update_hosts_with_org_slug_label(domains): + for domain in domains: + update_request = {"labels": [domain["orgSlug"]]} + asset_filter = f"kind = host AND name = {domain.domain}" + update = EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + print(f'{update["id"]}: {update["state"]}') + + +def update_hosts_with_external_id(domains): + for domain in domains: + update_request = {"externalId": [domain["id"]]} + asset_filter = "kind = host AND name = " + domain["domain"] + update = EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + print(f'{update["id"]}: {update["state"]}') + + +def label_assets(assets, label): + update_ids = [] + for asset in assets: + update_request = {"labels": {f"{label}": True}} + asset_filter = f"uuid = {asset['AssetUuid']}" + update = EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + update_ids.append(update["id"]) + print(f"{asset['AssetName']} labeled with {label}") + + # Using the `tasks` EASM_CLIENT, we can view the progress of each update using the `get` method + for update_id in update_ids: + update = EASM_CLIENT.tasks.get(update_id) + print(f'{update["id"]}: {update["state"]}') + + +def label_asset(asset, label): + update_request = {"labels": {f"{label}": True}} + asset_filter = f"uuid = {asset['AssetUuid']}" + update = EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + print(f'{update["id"]}: {update["state"]}') diff --git a/azure-defender-easm/scripts/easm_view_assets.py b/azure-defender-easm/scripts/easm_view_assets.py new file mode 100644 index 0000000000..472492e20e --- /dev/null +++ b/azure-defender-easm/scripts/easm_view_assets.py @@ -0,0 +1,102 @@ +from easm_client import EASM_CLIENT +from easm_discover_assets import list_disco_group_runs + + +def get_login_pages(): + login_pages = [] + for asset in EASM_CLIENT.assets.list( + filter="kind = page AND (url ~ /login OR url ~ /log-in OR url ~ /signin OR url ~ /sign-in) AND url !~ api.canada.ca", + ): + # print asset if it has a login page + if ".gc.ca" in asset["name"] or ".canada.ca" in asset["name"]: + print(asset["name"]) + login_pages.append(asset) + + return login_pages + + +def get_register_pages(): + register_pages = [] + for asset in EASM_CLIENT.assets.list( + filter="kind = page AND (url ~/register OR url ~/signup OR url ~/sign-up) AND url !~ api.canada.ca", + ): + # print asset if it has a login page + if ".gc.ca" in asset["name"] or ".canada.ca" in asset["name"]: + print(asset["name"]) + register_pages.append(asset) + + return register_pages + + +def get_web_components(asset): + return asset["asset"]["webComponents"] + + +def get_cookies(asset): + return asset["asset"]["cookies"] + + +def enumerate_attributes_types(): + type_enums = set() + for asset in EASM_CLIENT.assets.list(filter="kind = page AND state = confirmed"): + length = len(type_enums) + try: + attributes = asset["asset"]["attributes"] + for attribute in attributes: + type_enums.add(attribute["attributeType"]) + except KeyError: + pass + + if len(type_enums) > length: + print(type_enums) + + return type_enums + + +def enumerate_wc_types(): + type_enums = set() + for asset in EASM_CLIENT.assets.list(filter="kind = host AND state = confirmed"): + length = len(type_enums) + try: + web_components = asset["asset"]["webComponents"] + for wc in web_components: + type_enums.add(wc["type"]) + except KeyError: + pass + + if len(type_enums) > length: + print(type_enums) + + return type_enums + + +def find_gc_hosts(): + assets = [] + for asset in EASM_CLIENT.assets.list( + filter="kind = host AND state = confirmed AND wildcard = false", + ): + if ".gc.ca" in asset["name"] or ".canada.ca" in asset["name"]: + print(asset["name"]) + assets.append(asset) + return assets + + +def find_disco_group_assets(group_name): + group_runs = list_disco_group_runs(group_name) + + # sort by most recent run + group_runs = sorted(group_runs, key=lambda k: k["submittedDate"], reverse=True) + latest_run = group_runs[0] + print(f"Latest run: {latest_run}") + + assets = [] + for asset in EASM_CLIENT.assets.list( + filter=f"kind = host AND state = confirmed AND wildcard = false", + ): + print(asset["name"]) + assets.append(asset) + return assets + + +if __name__ == "__main__": + find_disco_group_assets("Elections") diff --git a/azure-defender-easm/scripts/kusto_client.py b/azure-defender-easm/scripts/kusto_client.py new file mode 100644 index 0000000000..b1abf1be16 --- /dev/null +++ b/azure-defender-easm/scripts/kusto_client.py @@ -0,0 +1,21 @@ +from azure.kusto.data import KustoClient, KustoConnectionStringBuilder + +import os +from dotenv import load_dotenv + +load_dotenv() + +KUSTO_CLUSTER = os.getenv("KUSTO_CLUSTER") +REGION = os.getenv("REGION") +KUSTO_DATABASE = os.getenv("KUSTO_DATABASE") +CLIENT_ID = os.getenv("CLIENT_ID") +CLIENT_SECRET = os.getenv("CLIENT_SECRET") +AUTHORITY_ID = os.getenv("TENANT_ID") + +KCSB_DATA = KustoConnectionStringBuilder.with_aad_application_key_authentication( + f"https://{KUSTO_CLUSTER}.{REGION}.kusto.windows.net", + CLIENT_ID, + CLIENT_SECRET, + AUTHORITY_ID, +) +KUSTO_CLIENT = KustoClient(KCSB_DATA) diff --git a/azure-defender-easm/scripts/kusto_queries.py b/azure-defender-easm/scripts/kusto_queries.py new file mode 100644 index 0000000000..07ee98b2ec --- /dev/null +++ b/azure-defender-easm/scripts/kusto_queries.py @@ -0,0 +1,120 @@ +from kusto_client import KUSTO_CLIENT, KUSTO_DATABASE +from azure.kusto.data.helpers import dataframe_from_result_table + + +def get_host_asset(host_name): + query = f""" + declare query_parameters(hostName:string = '{host_name}'); + EasmHostAsset + | where Host == hostName + | limit 1 + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def get_page_asset(page_name): + query = f""" + declare query_parameters(pageName:string = '{page_name}'); + EasmPageAsset + | where Host == pageName + | take 1 + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def get_host_assets(): + query = """ + EasmAsset + | where AssetName endswith '.gc.ca' or AssetName endswith '.canada.ca' + | where AssetType == 'HOST' + | summarize by AssetName, AssetUuid + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def get_host_assets_by_labels(label): + query = f""" + declare query_parameters(label:string = '{label}'); + EasmAsset + | where AssetName endswith '.gc.ca' or AssetName endswith '.canada.ca' + | where AssetType == 'HOST' + | where Labels has label + | summarize by AssetName, AssetUuid, Labels + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def get_hosts_with_ddos_protection(): + query = """ + EasmHostAsset + | where AssetName endswith '.gc.ca' or AssetName endswith '.canada.ca' + | where WebComponents has 'DDOS Protection' + | summarize by AssetName + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def get_hosts_without_ddos_protection(): + query = """ + EasmHostAsset + | where AssetName endswith '.gc.ca' or AssetName endswith '.canada.ca' + | where WebComponents !has 'DDOS Protection' + | summarize by AssetName + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def host_has_ddos_protection(domain): + query = f""" + declare query_parameters(domain:string = '{domain}'); + EasmHostAsset + | where AssetName == domain + | where WebComponents has 'DDOS Protection' + | project AssetName + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return len(data) > 0 + + +def get_unlabelled_org_assets_from_root(root): + query = f""" + declare query_parameters(domainRoot:string = '{root}'); + EasmAsset + | where TimeGeneratedValue > ago(24h) + | where AssetType == 'HOST' + | where AssetName == domainRoot or AssetName endswith '.' + domainRoot + | where Labels == '[]' + | project AssetName, AssetUuid, Labels + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data diff --git a/azure-defender-easm/scripts/replace_org_label_for_domain_root.py b/azure-defender-easm/scripts/replace_org_label_for_domain_root.py new file mode 100644 index 0000000000..62a767884b --- /dev/null +++ b/azure-defender-easm/scripts/replace_org_label_for_domain_root.py @@ -0,0 +1,41 @@ +from easm_client import EASM_CLIENT +from kusto_client import KUSTO_CLIENT, KUSTO_DATABASE +from azure.kusto.data.helpers import dataframe_from_result_table + + +def get_labelled_assets_with_root(root, label): + query = f""" + declare query_parameters(root:string = '{root}', label:string = '{label}'); + EasmAsset + | where AssetName == root or AssetName endswith '.' + root + | where Labels has label + | summarize by AssetName, AssetUuid, Labels + """ + response = KUSTO_CLIENT.execute(KUSTO_DATABASE, query) + data = dataframe_from_result_table(response.primary_results[0]).to_dict( + orient="records" + ) + return data + + +def switch_org_label_for_root(root, old_label, new_label): + # Get assets with old label + assets = get_labelled_assets_with_root(root, old_label) + # Remove old label + print("Removing old label") + for asset in assets: + update_request = {"labels": {f"{old_label}": False}} + asset_filter = f"uuid = {asset['uuid']}" + update = EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + print(f'{update["id"]}: {update["state"]}') + # Add new label + print("Adding new label") + for asset in assets: + update_request = {"labels": {f"{new_label}": True}} + asset_filter = f"uuid = {asset['uuid']}" + update = EASM_CLIENT.assets.update(body=update_request, filter=asset_filter) + print(f'{update["id"]}: {update["state"]}') + + +if __name__ == "__main__": + switch_org_label_for_root() diff --git a/azure-defender-easm/scripts/requirements.txt b/azure-defender-easm/scripts/requirements.txt new file mode 100644 index 0000000000..b8db2b9dbe --- /dev/null +++ b/azure-defender-easm/scripts/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.16.1 +azure-defender-easm==1.0.0b1 +azure-kusto-data==4.3.1 +python-dotenv==1.0.0 +python-arango==7.3.4 +nats-py==2.6.0 \ No newline at end of file diff --git a/ci/Dockerfile b/ci/Dockerfile index 62a72ea6e6..345577a5f5 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:21.04 +FROM ubuntu:22.04 # Python, don't write bytecode! ENV PYTHONDONTWRITEBYTECODE 1 diff --git a/ci/README.md b/ci/README.md index 2ac2a0ea16..349d814244 100644 --- a/ci/README.md +++ b/ci/README.md @@ -6,11 +6,11 @@ You can see it in the cloudbuild.yaml files under the name "track-compliance/ci" To build a copy of this image: ```sh -docker build -t gcr.io/track-compliance/ci . +docker build -t northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci . ``` To push to the registry you will need to configure docker with proper credentials. ```sh gcloud auth configure-docker -docker push gcr.io/track-compliance/ci +docker push northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci ``` diff --git a/ci/cloudbuild.yaml b/ci/cloudbuild.yaml index e07da8bb77..93dd5a60d3 100644 --- a/ci/cloudbuild.yaml +++ b/ci/cloudbuild.yaml @@ -1,29 +1,23 @@ steps: - - - name: 'gcr.io/cloud-builders/docker' - id: build-if-master - entrypoint: 'bash' + - name: "gcr.io/cloud-builders/docker" + id: build + entrypoint: "bash" dir: ci args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then - docker build -t gcr.io/track-compliance/ci . - else - exit 0 - fi + - "-c" + - | + docker build -t northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci . - - name: 'gcr.io/cloud-builders/docker' + - name: "gcr.io/cloud-builders/docker" id: push-if-master - entrypoint: 'bash' + entrypoint: "bash" dir: ci args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then - docker push gcr.io/track-compliance/ci - else - exit 0 - fi + - "-c" + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + docker push northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci + else + exit 0 + fi diff --git a/clients/python/Pipfile b/clients/python/Pipfile index 8170440312..67673719f8 100644 --- a/clients/python/Pipfile +++ b/clients/python/Pipfile @@ -5,9 +5,10 @@ name = "pypi" [packages] gql = ">=3.0.0a5" -aiohttp = "*" +aiohttp = "==3.11.7" websockets = "*" python-slugify = "*" +cryptography = "~=44.0.1" [dev-packages] black = "*" diff --git a/clients/python/Pipfile.lock b/clients/python/Pipfile.lock index 344113f026..8cd2ab30da 100644 --- a/clients/python/Pipfile.lock +++ b/clients/python/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "93785b304e403690a81181eefb5e9dba812a3ca03385957afde2e7cad2fccbcf" + "sha256": "1b2c6695e94c627a471b8b11bfda20644c0c792b303c7d049a4496f33d2eabdf" }, "pipfile-spec": 6, "requires": {}, @@ -14,146 +14,604 @@ ] }, "default": { + "aiohappyeyeballs": { + "hashes": [ + "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586", + "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4.3" + }, "aiohttp": { "hashes": [ - "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe", - "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe", - "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5", - "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8", - "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd", - "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb", - "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c", - "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87", - "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0", - "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290", - "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5", - "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287", - "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde", - "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf", - "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8", - "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16", - "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf", - "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809", - "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213", - "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f", - "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013", - "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b", - "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9", - "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5", - "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb", - "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df", - "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4", - "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439", - "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f", - "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22", - "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f", - "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5", - "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970", - "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009", - "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc", - "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a", - "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95" + "sha256:018f1b04883a12e77e7fc161934c0f298865d3a484aea536a6a2ca8d909f0ba0", + "sha256:01a8aca4af3da85cea5c90141d23f4b0eee3cbecfd33b029a45a80f28c66c668", + "sha256:04b0cc74d5a882c9dacaeeccc1444f0233212b6f5be8bc90833feef1e1ce14b9", + "sha256:0de6466b9d742b4ee56fe1b2440706e225eb48c77c63152b1584864a236e7a50", + "sha256:12724f3a211fa243570e601f65a8831372caf1a149d2f1859f68479f07efec3d", + "sha256:12e4d45847a174f77b2b9919719203769f220058f642b08504cf8b1cf185dacf", + "sha256:17829f37c0d31d89aa6b8b010475a10233774771f9b6dc2cc352ea4f8ce95d9a", + "sha256:1a17f6a230f81eb53282503823f59d61dff14fb2a93847bf0399dc8e87817307", + "sha256:1cf03d27885f8c5ebf3993a220cc84fc66375e1e6e812731f51aab2b2748f4a6", + "sha256:1fbf41a6bbc319a7816ae0f0177c265b62f2a59ad301a0e49b395746eb2a9884", + "sha256:2257bdd5cf54a4039a4337162cd8048f05a724380a2283df34620f55d4e29341", + "sha256:24054fce8c6d6f33a3e35d1c603ef1b91bbcba73e3f04a22b4f2f27dac59b347", + "sha256:241a6ca732d2766836d62c58c49ca7a93d08251daef0c1e3c850df1d1ca0cbc4", + "sha256:28c7af3e50e5903d21d7b935aceed901cc2475463bc16ddd5587653548661fdb", + "sha256:351849aca2c6f814575c1a485c01c17a4240413f960df1bf9f5deb0003c61a53", + "sha256:3ce18f703b7298e7f7633efd6a90138d99a3f9a656cb52c1201e76cb5d79cf08", + "sha256:3d1c9c15d3999107cbb9b2d76ca6172e6710a12fda22434ee8bd3f432b7b17e8", + "sha256:3dd3e7e7c9ef3e7214f014f1ae260892286647b3cf7c7f1b644a568fd410f8ca", + "sha256:43bfd25113c1e98aec6c70e26d5f4331efbf4aa9037ba9ad88f090853bf64d7f", + "sha256:43dd89a6194f6ab02a3fe36b09e42e2df19c211fc2050ce37374d96f39604997", + "sha256:481f10a1a45c5f4c4a578bbd74cff22eb64460a6549819242a87a80788461fba", + "sha256:4ba8d043fed7ffa117024d7ba66fdea011c0e7602327c6d73cacaea38abe4491", + "sha256:4bb7493c3e3a36d3012b8564bd0e2783259ddd7ef3a81a74f0dbfa000fce48b7", + "sha256:4c1a6309005acc4b2bcc577ba3b9169fea52638709ffacbd071f3503264620da", + "sha256:4dda726f89bfa5c465ba45b76515135a3ece0088dfa2da49b8bb278f3bdeea12", + "sha256:53c921b58fdc6485d6b2603e0132bb01cd59b8f0620ffc0907f525e0ba071687", + "sha256:5578cf40440eafcb054cf859964bc120ab52ebe0e0562d2b898126d868749629", + "sha256:59ee1925b5a5efdf6c4e7be51deee93984d0ac14a6897bd521b498b9916f1544", + "sha256:670847ee6aeb3a569cd7cdfbe0c3bec1d44828bbfbe78c5d305f7f804870ef9e", + "sha256:78c657ece7a73b976905ab9ec8be9ef2df12ed8984c24598a1791c58ce3b4ce4", + "sha256:7a9318da4b4ada9a67c1dd84d1c0834123081e746bee311a16bb449f363d965e", + "sha256:7b2f8107a3c329789f3c00b2daad0e35f548d0a55cda6291579136622099a46e", + "sha256:7ea4490360b605804bea8173d2d086b6c379d6bb22ac434de605a9cbce006e7d", + "sha256:8360c7cc620abb320e1b8d603c39095101391a82b1d0be05fb2225471c9c5c52", + "sha256:875f7100ce0e74af51d4139495eec4025affa1a605280f23990b6434b81df1bd", + "sha256:8bedb1f6cb919af3b6353921c71281b1491f948ca64408871465d889b4ee1b66", + "sha256:8d20cfe63a1c135d26bde8c1d0ea46fd1200884afbc523466d2f1cf517d1fe33", + "sha256:9202f184cc0582b1db15056f2225ab4c1e3dac4d9ade50dd0613ac3c46352ac2", + "sha256:9acfc7f652b31853eed3b92095b0acf06fd5597eeea42e939bd23a17137679d5", + "sha256:9d18a8b44ec8502a7fde91446cd9c9b95ce7c49f1eacc1fb2358b8907d4369fd", + "sha256:9e67531370a3b07e49b280c1f8c2df67985c790ad2834d1b288a2f13cd341c5f", + "sha256:9ee6a4cdcbf54b8083dc9723cdf5f41f722c00db40ccf9ec2616e27869151129", + "sha256:a7d9a606355655617fee25dd7e54d3af50804d002f1fd3118dd6312d26692d70", + "sha256:aa3705a8d14de39898da0fbad920b2a37b7547c3afd2a18b9b81f0223b7d0f68", + "sha256:b7215bf2b53bc6cb35808149980c2ae80a4ae4e273890ac85459c014d5aa60ac", + "sha256:badda65ac99555791eed75e234afb94686ed2317670c68bff8a4498acdaee935", + "sha256:bf0e6cce113596377cadda4e3ac5fb89f095bd492226e46d91b4baef1dd16f60", + "sha256:c171fc35d3174bbf4787381716564042a4cbc008824d8195eede3d9b938e29a8", + "sha256:c1f6490dd1862af5aae6cfcf2a274bffa9a5b32a8f5acb519a7ecf5a99a88866", + "sha256:c25b74a811dba37c7ea6a14d99eb9402d89c8d739d50748a75f3cf994cf19c43", + "sha256:c6095aaf852c34f42e1bd0cf0dc32d1e4b48a90bfb5054abdbb9d64b36acadcb", + "sha256:c63f898f683d1379b9be5afc3dd139e20b30b0b1e0bf69a3fc3681f364cf1629", + "sha256:cd8d62cab363dfe713067027a5adb4907515861f1e4ce63e7be810b83668b847", + "sha256:ce91a24aac80de6be8512fb1c4838a9881aa713f44f4e91dd7bb3b34061b497d", + "sha256:cea52d11e02123f125f9055dfe0ccf1c3857225fb879e4a944fae12989e2aef2", + "sha256:cf4efa2d01f697a7dbd0509891a286a4af0d86902fc594e20e3b1712c28c0106", + "sha256:d2fa6fc7cc865d26ff42480ac9b52b8c9b7da30a10a6442a9cdf429de840e949", + "sha256:d329300fb23e14ed1f8c6d688dfd867d1dcc3b1d7cd49b7f8c5b44e797ce0932", + "sha256:d6177077a31b1aecfc3c9070bd2f11419dbb4a70f30f4c65b124714f525c2e48", + "sha256:db37248535d1ae40735d15bdf26ad43be19e3d93ab3f3dad8507eb0f85bb8124", + "sha256:db70a47987e34494b451a334605bee57a126fe8d290511349e86810b4be53b01", + "sha256:dcefcf2915a2dbdbce37e2fc1622129a1918abfe3d06721ce9f6cdac9b6d2eaa", + "sha256:dda3ed0a7869d2fa16aa41f9961ade73aa2c2e3b2fcb0a352524e7b744881889", + "sha256:e0bf378db07df0a713a1e32381a1b277e62ad106d0dbe17b5479e76ec706d720", + "sha256:e13a05db87d3b241c186d0936808d0e4e12decc267c617d54e9c643807e968b6", + "sha256:e143b0ef9cb1a2b4f74f56d4fbe50caa7c2bb93390aff52f9398d21d89bc73ea", + "sha256:e22d1721c978a6494adc824e0916f9d187fa57baeda34b55140315fa2f740184", + "sha256:e5522ee72f95661e79db691310290c4618b86dff2d9b90baedf343fd7a08bf79", + "sha256:e993676c71288618eb07e20622572b1250d8713e7e00ab3aabae28cb70f3640d", + "sha256:ee9afa1b0d2293c46954f47f33e150798ad68b78925e3710044e0d67a9487791", + "sha256:f1ac5462582d6561c1c1708853a9faf612ff4e5ea5e679e99be36143d6eabd8e", + "sha256:f5022504adab881e2d801a88b748ea63f2a9d130e0b2c430824682a96f6534be", + "sha256:f5b973cce96793725ef63eb449adfb74f99c043c718acb76e0d2a447ae369962", + "sha256:f7c58a240260822dc07f6ae32a0293dd5bccd618bb2d0f36d51c5dbd526f89c0", + "sha256:fc6da202068e0a268e298d7cd09b6e9f3997736cd9b060e2750963754552a0a9", + "sha256:fdadc3f6a32d6eca45f9a900a254757fd7855dfb2d8f8dcf0e88f0fae3ff8eb1" ], "index": "pypi", - "version": "==3.7.4.post0" + "markers": "python_version >= '3.9'", + "version": "==3.11.7" + }, + "aiosignal": { + "hashes": [ + "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, + "anyio": { + "hashes": [ + "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", + "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.0" }, "async-timeout": { "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" + "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", + "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028" ], - "markers": "python_full_version >= '3.5.3'", - "version": "==3.0.1" + "markers": "python_version < '3.11'", + "version": "==4.0.3" }, "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" + "markers": "python_version >= '3.7'", + "version": "==24.2.0" }, - "chardet": { + "backoff": { "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", + "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.0.0" + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==2.2.1" + }, + "cffi": { + "hashes": [ + "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", + "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", + "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", + "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", + "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", + "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", + "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", + "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", + "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", + "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", + "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", + "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", + "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.17.1" + }, + "cryptography": { + "hashes": [ + "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7", + "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3", + "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183", + "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69", + "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a", + "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62", + "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911", + "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7", + "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a", + "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41", + "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83", + "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12", + "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864", + "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf", + "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c", + "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2", + "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b", + "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0", + "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4", + "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9", + "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008", + "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862", + "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009", + "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7", + "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f", + "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026", + "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f", + "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd", + "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420", + "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14", + "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00" + ], + "index": "pypi", + "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==44.0.1" + }, + "exceptiongroup": { + "hashes": [ + "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", + "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" + ], + "markers": "python_version < '3.11'", + "version": "==1.2.1" + }, + "frozenlist": { + "hashes": [ + "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", + "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", + "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6", + "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", + "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", + "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f", + "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", + "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", + "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", + "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", + "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", + "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2", + "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c", + "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336", + "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", + "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", + "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b", + "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c", + "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10", + "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08", + "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", + "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", + "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f", + "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10", + "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", + "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", + "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", + "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", + "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d", + "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923", + "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", + "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", + "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", + "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0", + "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", + "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", + "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", + "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", + "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0", + "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", + "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", + "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", + "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3", + "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", + "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", + "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604", + "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", + "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", + "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", + "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", + "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", + "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", + "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", + "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", + "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3", + "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", + "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", + "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", + "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf", + "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", + "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", + "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171", + "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", + "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", + "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", + "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972", + "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", + "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", + "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9", + "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411", + "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723", + "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", + "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b", + "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99", + "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e", + "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", + "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", + "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", + "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", + "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", + "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca", + "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", + "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", + "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", + "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", + "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307", + "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e", + "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", + "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", + "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", + "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", + "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" }, "gql": { "hashes": [ - "sha256:bdcbf60bc37b11d6d2f2ed271f69292c4e96d56df7000ba1dad52e487330bdce" + "sha256:c681d2273cc8164c0d6557c2d6fd4df190a009706a2044cd640be6a24526318e", + "sha256:f1a4fc06186f25e5b4b5abaf3af359bc7ac65b38bcaa705b6507cd29dec2ecbf" ], "index": "pypi", - "version": "==3.0.0a6" + "version": "==3.6.0b2" }, "graphql-core": { "hashes": [ - "sha256:91d96ef0e86665777bb7115d3bbb6b0326f43dc7dbcdd60da5486a27a50cfb11", - "sha256:a755635d1d364a17e8d270347000722351aaa03f1ab7d280878aae82fc68b1f3" + "sha256:9d72ed2c4ac93682fe55a0a2939548c1b3d23bd7e2ad76f7f8faef4d99d606ec", + "sha256:f3a8ab44a436651608b8e0335800b175c4bb3a7bf0217bbc97babf6d9517218a" ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==3.1.5" + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==3.3.0a5" }, "idna": { "hashes": [ - "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", - "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.2" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "multidict": { "hashes": [ - "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a", - "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93", - "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632", - "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656", - "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79", - "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7", - "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d", - "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5", - "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224", - "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26", - "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea", - "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348", - "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6", - "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76", - "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1", - "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f", - "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952", - "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a", - "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37", - "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9", - "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359", - "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8", - "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da", - "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3", - "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d", - "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf", - "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841", - "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d", - "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93", - "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f", - "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647", - "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635", - "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456", - "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda", - "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5", - "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", - "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" + "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", + "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", + "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", + "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", + "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", + "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", + "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", + "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", + "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", + "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", + "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", + "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", + "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", + "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", + "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", + "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", + "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", + "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", + "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", + "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", + "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", + "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", + "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", + "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", + "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", + "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", + "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", + "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", + "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", + "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", + "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", + "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", + "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", + "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", + "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", + "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", + "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", + "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", + "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", + "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", + "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", + "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", + "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", + "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", + "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", + "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", + "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", + "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", + "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", + "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", + "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", + "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", + "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", + "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", + "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", + "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", + "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", + "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", + "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", + "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", + "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", + "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", + "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", + "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", + "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", + "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", + "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", + "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", + "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", + "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", + "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", + "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", + "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", + "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", + "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", + "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", + "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", + "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", + "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", + "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", + "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", + "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", + "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", + "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", + "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", + "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", + "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", + "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", + "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", + "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", + "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", + "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" + ], + "markers": "python_version >= '3.8'", + "version": "==6.1.0" + }, + "propcache": { + "hashes": [ + "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", + "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", + "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", + "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb", + "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b", + "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", + "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957", + "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68", + "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f", + "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798", + "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418", + "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6", + "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162", + "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", + "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", + "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", + "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", + "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110", + "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", + "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8", + "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638", + "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a", + "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", + "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2", + "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", + "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850", + "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", + "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", + "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", + "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89", + "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87", + "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", + "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4", + "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861", + "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", + "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", + "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b", + "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", + "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1", + "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de", + "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354", + "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563", + "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5", + "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", + "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9", + "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12", + "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", + "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", + "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71", + "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9", + "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed", + "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336", + "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90", + "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063", + "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad", + "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6", + "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8", + "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", + "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2", + "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", + "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", + "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d", + "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df", + "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b", + "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178", + "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2", + "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630", + "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48", + "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61", + "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", + "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb", + "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", + "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6", + "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562", + "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b", + "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58", + "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db", + "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99", + "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37", + "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", + "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", + "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d", + "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04", + "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", + "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", + "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394", + "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea", + "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", + "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1", + "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", + "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577", + "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7", + "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", + "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d", + "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", + "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d", + "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016", + "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504" + ], + "markers": "python_version >= '3.8'", + "version": "==0.2.0" + }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], - "markers": "python_version >= '3.6'", - "version": "==5.1.0" + "markers": "python_version >= '3.8'", + "version": "==2.22" }, "python-slugify": { "hashes": [ - "sha256:6d8c5df75cd4a7c3a2d21e257633de53f52ab0265cd2d1dc62a730e8194a7380", - "sha256:f13383a0b9fcbe649a1892b9c8eb4f8eab1d6d84b84bb7a624317afa98159cab" + "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", + "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856" ], "index": "pypi", - "version": "==5.0.2" + "markers": "python_version >= '3.7'", + "version": "==8.0.4" + }, + "sniffio": { + "hashes": [ + "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" }, "text-unidecode": { "hashes": [ @@ -164,713 +622,950 @@ }, "typing-extensions": { "hashes": [ - "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", - "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", - "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + "sha256:be199d06d8f09ca2c9425e3aa04a9afba33e892fe079dea959e72df7f8442343", + "sha256:f933a7b288a919ca97adbff656e52ff81f7ff25d98a2aabb9355ca4090f772fe" ], - "version": "==3.10.0.0" + "markers": "python_version < '3.11'", + "version": "==4.12.0rc1" }, "websockets": { "hashes": [ - "sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc", - "sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e", - "sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135", - "sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02", - "sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3", - "sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf", - "sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b", - "sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2", - "sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af", - "sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d", - "sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880", - "sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077", - "sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f", - "sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec", - "sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25", - "sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0", - "sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe", - "sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a", - "sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb", - "sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d", - "sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857", - "sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c", - "sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0", - "sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40", - "sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4", - "sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20", - "sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314", - "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da", - "sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58", - "sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2", - "sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd", - "sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a", - "sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd" + "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", + "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", + "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", + "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", + "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", + "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", + "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", + "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", + "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", + "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", + "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", + "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", + "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", + "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", + "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", + "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", + "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", + "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", + "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", + "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", + "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", + "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", + "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", + "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", + "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", + "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", + "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", + "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", + "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", + "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", + "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", + "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", + "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", + "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", + "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", + "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", + "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", + "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", + "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", + "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", + "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", + "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", + "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", + "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", + "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", + "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", + "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", + "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", + "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", + "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", + "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", + "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", + "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", + "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", + "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", + "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", + "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", + "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", + "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", + "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", + "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", + "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", + "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", + "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", + "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", + "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", + "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", + "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", + "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", + "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", + "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", + "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" ], "index": "pypi", - "version": "==9.1" + "markers": "python_version >= '3.8'", + "version": "==12.0" }, "yarl": { "hashes": [ - "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", - "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434", - "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366", - "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3", - "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec", - "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959", - "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e", - "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c", - "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6", - "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a", - "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6", - "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424", - "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e", - "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f", - "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50", - "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2", - "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc", - "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4", - "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970", - "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10", - "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0", - "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406", - "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896", - "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643", - "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721", - "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478", - "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724", - "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e", - "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8", - "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96", - "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25", - "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76", - "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2", - "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2", - "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c", - "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", - "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" - ], - "markers": "python_version >= '3.6'", - "version": "==1.6.3" + "sha256:01be8688fc211dc237e628fcc209dda412d35de7642453059a0553747018d075", + "sha256:039c299a0864d1f43c3e31570045635034ea7021db41bf4842693a72aca8df3a", + "sha256:074fee89caab89a97e18ef5f29060ef61ba3cae6cd77673acc54bfdd3214b7b7", + "sha256:13aaf2bdbc8c86ddce48626b15f4987f22e80d898818d735b20bd58f17292ee8", + "sha256:14408cc4d34e202caba7b5ac9cc84700e3421a9e2d1b157d744d101b061a4a88", + "sha256:1db1537e9cb846eb0ff206eac667f627794be8b71368c1ab3207ec7b6f8c5afc", + "sha256:1ece25e2251c28bab737bdf0519c88189b3dd9492dc086a1d77336d940c28ced", + "sha256:1ff116f0285b5c8b3b9a2680aeca29a858b3b9e0402fc79fd850b32c2bcb9f8b", + "sha256:205de377bd23365cd85562c9c6c33844050a93661640fda38e0567d2826b50df", + "sha256:20d95535e7d833889982bfe7cc321b7f63bf8879788fee982c76ae2b24cfb715", + "sha256:20de4a8b04de70c49698dc2390b7fd2d18d424d3b876371f9b775e2b462d4b41", + "sha256:2d90f2e4d16a5b0915ee065218b435d2ef619dd228973b1b47d262a6f7cd8fa5", + "sha256:2e6b4466714a73f5251d84b471475850954f1fa6acce4d3f404da1d55d644c34", + "sha256:309f8d27d6f93ceeeb80aa6980e883aa57895270f7f41842b92247e65d7aeddf", + "sha256:32141e13a1d5a48525e519c9197d3f4d9744d818d5c7d6547524cc9eccc8971e", + "sha256:34176bfb082add67cb2a20abd85854165540891147f88b687a5ed0dc225750a0", + "sha256:38b39b7b3e692b6c92b986b00137a3891eddb66311b229d1940dcbd4f025083c", + "sha256:3a3709450a574d61be6ac53d582496014342ea34876af8dc17cc16da32826c9a", + "sha256:3adaaf9c6b1b4fc258584f4443f24d775a2086aee82d1387e48a8b4f3d6aecf6", + "sha256:3f576ed278860df2721a5d57da3381040176ef1d07def9688a385c8330db61a1", + "sha256:42ba84e2ac26a3f252715f8ec17e6fdc0cbf95b9617c5367579fafcd7fba50eb", + "sha256:454902dc1830d935c90b5b53c863ba2a98dcde0fbaa31ca2ed1ad33b2a7171c6", + "sha256:466d31fd043ef9af822ee3f1df8fdff4e8c199a7f4012c2642006af240eade17", + "sha256:49a98ecadc5a241c9ba06de08127ee4796e1009555efd791bac514207862b43d", + "sha256:4d26f1fa9fa2167bb238f6f4b20218eb4e88dd3ef21bb8f97439fa6b5313e30d", + "sha256:52c136f348605974c9b1c878addd6b7a60e3bf2245833e370862009b86fa4689", + "sha256:536a7a8a53b75b2e98ff96edb2dfb91a26b81c4fed82782035767db5a465be46", + "sha256:576d258b21c1db4c6449b1c572c75d03f16a482eb380be8003682bdbe7db2f28", + "sha256:609ffd44fed2ed88d9b4ef62ee860cf86446cf066333ad4ce4123505b819e581", + "sha256:67b336c15e564d76869c9a21316f90edf546809a5796a083b8f57c845056bc01", + "sha256:685cc37f3f307c6a8e879986c6d85328f4c637f002e219f50e2ef66f7e062c1d", + "sha256:6a49ad0102c0f0ba839628d0bf45973c86ce7b590cdedf7540d5b1833ddc6f00", + "sha256:6fb64dd45453225f57d82c4764818d7a205ee31ce193e9f0086e493916bd4f72", + "sha256:701bb4a8f4de191c8c0cc9a1e6d5142f4df880e9d1210e333b829ca9425570ed", + "sha256:73553bbeea7d6ec88c08ad8027f4e992798f0abc459361bf06641c71972794dc", + "sha256:7520e799b1f84e095cce919bd6c23c9d49472deeef25fe1ef960b04cca51c3fc", + "sha256:7609b8462351c4836b3edce4201acb6dd46187b207c589b30a87ffd1813b48dc", + "sha256:7db9584235895a1dffca17e1c634b13870852094f6389b68dcc6338086aa7b08", + "sha256:7fa7d37f2ada0f42e0723632993ed422f2a679af0e200874d9d861720a54f53e", + "sha256:80741ec5b471fbdfb997821b2842c59660a1c930ceb42f8a84ba8ca0f25a66aa", + "sha256:8254dbfce84ee5d1e81051ee7a0f1536c108ba294c0fdb5933476398df0654f3", + "sha256:8b8d3e4e014fb4274f1c5bf61511d2199e263909fb0b8bda2a7428b0894e8dc6", + "sha256:8e1c18890091aa3cc8a77967943476b729dc2016f4cfe11e45d89b12519d4a93", + "sha256:9106025c7f261f9f5144f9aa7681d43867eed06349a7cfb297a1bc804de2f0d1", + "sha256:91b8fb9427e33f83ca2ba9501221ffaac1ecf0407f758c4d2f283c523da185ee", + "sha256:96404e8d5e1bbe36bdaa84ef89dc36f0e75939e060ca5cd45451aba01db02902", + "sha256:9b4c90c5363c6b0a54188122b61edb919c2cd1119684999d08cd5e538813a28e", + "sha256:a0509475d714df8f6d498935b3f307cd122c4ca76f7d426c7e1bb791bcd87eda", + "sha256:a173401d7821a2a81c7b47d4e7d5c4021375a1441af0c58611c1957445055056", + "sha256:a45d94075ac0647621eaaf693c8751813a3eccac455d423f473ffed38c8ac5c9", + "sha256:a5f72421246c21af6a92fbc8c13b6d4c5427dfd949049b937c3b731f2f9076bd", + "sha256:a64619a9c47c25582190af38e9eb382279ad42e1f06034f14d794670796016c0", + "sha256:a7ee6884a8848792d58b854946b685521f41d8871afa65e0d4a774954e9c9e89", + "sha256:ae38bd86eae3ba3d2ce5636cc9e23c80c9db2e9cb557e40b98153ed102b5a736", + "sha256:b026cf2c32daf48d90c0c4e406815c3f8f4cfe0c6dfccb094a9add1ff6a0e41a", + "sha256:b0a2074a37285570d54b55820687de3d2f2b9ecf1b714e482e48c9e7c0402038", + "sha256:b1a3297b9cad594e1ff0c040d2881d7d3a74124a3c73e00c3c71526a1234a9f7", + "sha256:b212452b80cae26cb767aa045b051740e464c5129b7bd739c58fbb7deb339e7b", + "sha256:b234a4a9248a9f000b7a5dfe84b8cb6210ee5120ae70eb72a4dcbdb4c528f72f", + "sha256:b4095c5019bb889aa866bf12ed4c85c0daea5aafcb7c20d1519f02a1e738f07f", + "sha256:b8e8c516dc4e1a51d86ac975b0350735007e554c962281c432eaa5822aa9765c", + "sha256:bd80ed29761490c622edde5dd70537ca8c992c2952eb62ed46984f8eff66d6e8", + "sha256:c083f6dd6951b86e484ebfc9c3524b49bcaa9c420cb4b2a78ef9f7a512bfcc85", + "sha256:c0f4808644baf0a434a3442df5e0bedf8d05208f0719cedcd499e168b23bfdc4", + "sha256:c4cb992d8090d5ae5f7afa6754d7211c578be0c45f54d3d94f7781c495d56716", + "sha256:c60e547c0a375c4bfcdd60eef82e7e0e8698bf84c239d715f5c1278a73050393", + "sha256:c73a6bbc97ba1b5a0c3c992ae93d721c395bdbb120492759b94cc1ac71bc6350", + "sha256:c893f8c1a6d48b25961e00922724732d00b39de8bb0b451307482dc87bddcd74", + "sha256:cd6ab7d6776c186f544f893b45ee0c883542b35e8a493db74665d2e594d3ca75", + "sha256:d89ae7de94631b60d468412c18290d358a9d805182373d804ec839978b120422", + "sha256:d9d4f5e471e8dc49b593a80766c2328257e405f943c56a3dc985c125732bc4cf", + "sha256:da206d1ec78438a563c5429ab808a2b23ad7bc025c8adbf08540dde202be37d5", + "sha256:dbf53db46f7cf176ee01d8d98c39381440776fcda13779d269a8ba664f69bec0", + "sha256:dd21c0128e301851de51bc607b0a6da50e82dc34e9601f4b508d08cc89ee7929", + "sha256:e2580c1d7e66e6d29d6e11855e3b1c6381971e0edd9a5066e6c14d79bc8967af", + "sha256:e3818eabaefb90adeb5e0f62f047310079d426387991106d4fbf3519eec7d90a", + "sha256:ed69af4fe2a0949b1ea1d012bf065c77b4c7822bad4737f17807af2adb15a73c", + "sha256:f172b8b2c72a13a06ea49225a9c47079549036ad1b34afa12d5491b881f5b993", + "sha256:f275ede6199d0f1ed4ea5d55a7b7573ccd40d97aee7808559e1298fe6efc8dbd", + "sha256:f7edeb1dcc7f50a2c8e08b9dc13a413903b7817e72273f00878cb70e766bdb3b", + "sha256:fa2c9cb607e0f660d48c54a63de7a9b36fef62f6b8bd50ff592ce1137e73ac7d", + "sha256:fe94d1de77c4cd8caff1bd5480e22342dbd54c93929f5943495d9c1e8abe9f42" + ], + "markers": "python_version >= '3.9'", + "version": "==1.18.0" } }, "develop": { "alabaster": { "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" + "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", + "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92" ], - "version": "==0.7.12" - }, - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" + "markers": "python_version >= '3.9'", + "version": "==0.7.16" }, "astroid": { "hashes": [ - "sha256:3975a0bd5373bdce166e60c851cfcbaf21ee96de80ec518c1f4cb3e94c3fb334", - "sha256:ab7f36e8a78b8e54a62028ba6beef7561db4cdb6f2a5009ecc44a6f42b5697ef" + "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c", + "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b" ], - "markers": "python_version ~= '3.6'", - "version": "==2.6.6" + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.8" }, - "attrs": { + "babel": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb", + "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" + "markers": "python_version >= '3.8'", + "version": "==2.15.0" }, - "babel": { + "backports.tarfile": { "hashes": [ - "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", - "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" + "sha256:73e0179647803d3726d82e76089d01d8549ceca9bace469953fcb4d97cf2d417", + "sha256:9c2ef9696cb73374f7164e17fc761389393ca76777036f5aad42e8b93fcd8009" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.1" + "markers": "python_version < '3.12'", + "version": "==1.1.1" }, "bandit": { "hashes": [ - "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07", - "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608" + "sha256:df6146ad73dd30e8cbda4e29689ddda48364e36ff655dbfc86998401fcf1721f", + "sha256:e00ad5a6bc676c0954669fe13818024d66b70e42cf5adb971480cf3b671e835f" ], "index": "pypi", - "version": "==1.7.0" + "markers": "python_version >= '3.9'", + "version": "==1.8.2" }, "black": { "hashes": [ - "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116", - "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219" + "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474", + "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1", + "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0", + "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8", + "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96", + "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1", + "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04", + "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021", + "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94", + "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d", + "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c", + "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7", + "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c", + "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc", + "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7", + "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d", + "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c", + "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741", + "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce", + "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb", + "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063", + "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e" ], "index": "pypi", - "version": "==21.7b0" - }, - "bleach": { - "hashes": [ - "sha256:c1685a132e6a9a38bf93752e5faab33a9517a6c0bb2f37b785e47bf253bdb51d", - "sha256:ffa9221c6ac29399cc50fcc33473366edd0cf8d5e2cbbbb63296dc327fb67cc8" - ], - "markers": "python_version >= '3.6'", - "version": "==4.0.0" + "markers": "python_version >= '3.8'", + "version": "==24.4.2" }, "certifi": { "hashes": [ - "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", - "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" + "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", + "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" ], - "version": "==2021.5.30" + "markers": "python_version >= '3.6'", + "version": "==2025.4.26" }, "cffi": { "hashes": [ - "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", - "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", - "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", - "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", - "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", - "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", - "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", - "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", - "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", - "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", - "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", - "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", - "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", - "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", - "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", - "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", - "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", - "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", - "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", - "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", - "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", - "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", - "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", - "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", - "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", - "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", - "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", - "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", - "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", - "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", - "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", - "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", - "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", - "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", - "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", - "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", - "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", - "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", - "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", - "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", - "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", - "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", - "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", - "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", - "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" - ], - "version": "==1.14.6" + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" }, "charset-normalizer": { "hashes": [ - "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", - "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" - ], - "markers": "python_version >= '3'", - "version": "==2.0.4" + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.2" }, "click": { "hashes": [ - "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", - "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.1" - }, - "colorama": { - "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.4.4" + "markers": "python_version >= '3.7'", + "version": "==8.1.7" }, "cryptography": { "hashes": [ - "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", - "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", - "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", - "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", - "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", - "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", - "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", - "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", - "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", - "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586", - "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3", - "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", - "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", - "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55", + "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785", + "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b", + "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886", + "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82", + "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1", + "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda", + "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f", + "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68", + "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60", + "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7", + "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd", + "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582", + "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc", + "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858", + "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b", + "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2", + "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678", + "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13", + "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4", + "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8", + "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604", + "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477", + "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e", + "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a", + "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9", + "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14", + "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda", + "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da", + "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562", + "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2", + "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.7" + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==42.0.7" }, - "docutils": { + "dill": { "hashes": [ - "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", - "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61" + "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a", + "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.17.1" + "markers": "python_version >= '3.11'", + "version": "==0.3.9" }, - "gitdb": { + "docutils": { "hashes": [ - "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", - "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" + "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", + "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2" ], - "markers": "python_version >= '3.4'", - "version": "==4.0.7" + "markers": "python_version >= '3.9'", + "version": "==0.21.2" }, - "gitpython": { + "exceptiongroup": { "hashes": [ - "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b", - "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8" + "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", + "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" ], - "markers": "python_version >= '3.6'", - "version": "==3.1.18" + "markers": "python_version < '3.11'", + "version": "==1.2.1" }, "idna": { "hashes": [ - "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", - "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.2" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "imagesize": { "hashes": [ - "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", - "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" + "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", + "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.2.0" + "version": "==1.4.1" }, "importlib-metadata": { "hashes": [ - "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f", - "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5" + "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" ], - "markers": "python_version >= '3.6'", - "version": "==4.6.4" + "markers": "python_version >= '3.8'", + "version": "==7.1.0" }, "iniconfig": { "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" ], - "version": "==1.1.1" + "markers": "python_version >= '3.7'", + "version": "==2.0.0" }, "isort": { "hashes": [ - "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899", - "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2" + "sha256:567954102bb47bb12e0fae62606570faacddd441e45683968c8d1734fb1af892", + "sha256:75d9d8a1438a9432a7d7b54f2d3b45cad9a4a0fdba43617d9873379704a8bdf1" + ], + "markers": "python_full_version >= '3.9.0'", + "version": "==6.0.0" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", - "version": "==5.9.3" + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266", + "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2" + ], + "markers": "python_version >= '3.8'", + "version": "==5.3.0" + }, + "jaraco.functools": { + "hashes": [ + "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664", + "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8" + ], + "markers": "python_version >= '3.8'", + "version": "==4.0.1" }, "jeepney": { "hashes": [ - "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac", - "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f" + "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", + "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755" ], "markers": "sys_platform == 'linux'", - "version": "==0.7.1" + "version": "==0.8.0" }, "jinja2": { "hashes": [ - "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", - "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], - "markers": "python_version >= '3.6'", - "version": "==3.0.1" + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==3.1.6" }, "keyring": { "hashes": [ - "sha256:b32397fd7e7063f8dd74a26db910c9862fc2109285fa16e3b5208bcb42a3e579", - "sha256:b7e0156667f5dcc73c1f63a518005cd18a4eb23fe77321194fefcc03748b21a4" + "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50", + "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b" ], - "markers": "python_version >= '3.6'", - "version": "==23.1.0" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653", - "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61", - "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2", - "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837", - "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3", - "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43", - "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726", - "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3", - "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587", - "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8", - "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a", - "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd", - "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f", - "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad", - "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4", - "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b", - "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf", - "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981", - "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741", - "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e", - "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", - "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.6.0" + "markers": "python_version >= '3.8'", + "version": "==25.2.1" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" }, "markupsafe": { "hashes": [ - "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", - "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", - "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", - "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", - "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", - "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", - "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", - "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", - "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", - "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", - "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", - "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", - "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", - "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", - "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", - "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", - "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", - "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", - "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", - "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", - "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", - "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", - "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", - "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", - "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", - "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", - "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", - "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", - "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", - "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", - "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", - "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", - "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", - "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", - "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", - "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", - "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", - "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", - "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", - "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", - "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", - "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", - "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", - "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", - "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", - "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", - "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", - "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", - "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", - "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", - "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", - "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", - "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", - "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" + ], + "markers": "python_version >= '3.9'", + "version": "==3.0.2" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" ], "markers": "python_version >= '3.6'", - "version": "==2.0.1" + "version": "==0.7.0" }, - "mccabe": { + "mdurl": { "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" ], - "version": "==0.6.1" + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1" + ], + "markers": "python_version >= '3.8'", + "version": "==10.2.0" }, "mypy-extensions": { "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" ], - "version": "==0.4.3" + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "nh3": { + "hashes": [ + "sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a", + "sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911", + "sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb", + "sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a", + "sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc", + "sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028", + "sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9", + "sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3", + "sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351", + "sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10", + "sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71", + "sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f", + "sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b", + "sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a", + "sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062", + "sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a" + ], + "version": "==0.2.17" }, "packaging": { "hashes": [ - "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", - "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" ], - "markers": "python_version >= '3.6'", - "version": "==21.0" + "markers": "python_version >= '3.7'", + "version": "==24.0" }, "pathspec": { "hashes": [ - "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", - "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" ], - "version": "==0.9.0" + "markers": "python_version >= '3.8'", + "version": "==0.12.1" }, "pbr": { "hashes": [ - "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd", - "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4" + "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24", + "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a" ], "markers": "python_version >= '2.6'", - "version": "==5.6.0" + "version": "==6.1.0" }, "pkginfo": { "hashes": [ - "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779", - "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd" + "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097" ], - "version": "==1.7.1" + "markers": "python_version >= '3.6'", + "version": "==1.10.0" }, - "pluggy": { + "platformdirs": { "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.13.1" + "markers": "python_version >= '3.8'", + "version": "==4.3.6" }, - "py": { + "pluggy": { "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.10.0" + "markers": "python_version >= '3.8'", + "version": "==1.5.0" }, "pycparser": { "hashes": [ - "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", - "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.20" + "markers": "python_version >= '3.8'", + "version": "==2.22" }, "pygments": { "hashes": [ - "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", - "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" + "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", + "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" ], - "markers": "python_version >= '3.5'", - "version": "==2.10.0" + "markers": "python_version >= '3.8'", + "version": "==2.19.1" }, "pylint": { "hashes": [ - "sha256:2e1a0eb2e8ab41d6b5dbada87f066492bb1557b12b76c47c2ee8aa8a11186594", - "sha256:8b838c8983ee1904b2de66cce9d0b96649a91901350e956d78f289c3bc87b48e" + "sha256:289e6a1eb27b453b08436478391a48cd53bb0efb824873f949e709350f3de018", + "sha256:74ae7a38b177e69a9b525d0794bd8183820bfa7eb68cc1bee6e8ed22a42be4ce" ], "index": "pypi", - "version": "==2.9.6" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.4" }, "pytest": { "hashes": [ - "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", - "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" + "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd", + "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1" ], "index": "pypi", - "version": "==6.2.4" + "markers": "python_version >= '3.8'", + "version": "==8.2.1" }, "pytest-mock": { "hashes": [ - "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3", - "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62" + "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", + "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0" ], "index": "pypi", - "version": "==3.6.1" - }, - "pytz": { - "hashes": [ - "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", - "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" - ], - "version": "==2021.1" + "markers": "python_version >= '3.8'", + "version": "==3.14.0" }, "pyyaml": { "hashes": [ - "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", - "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", - "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", - "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", - "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", - "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", - "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", - "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", - "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", - "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", - "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", - "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", - "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", - "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", - "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", - "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", - "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", - "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", - "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", - "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", - "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", - "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", - "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", - "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", - "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", - "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", - "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", - "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", - "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==5.4.1" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "readme-renderer": { "hashes": [ - "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c", - "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db" - ], - "version": "==29.0" - }, - "regex": { - "hashes": [ - "sha256:026beb631097a4a3def7299aa5825e05e057de3c6d72b139c37813bfa351274b", - "sha256:14caacd1853e40103f59571f169704367e79fb78fac3d6d09ac84d9197cadd16", - "sha256:16d9eaa8c7e91537516c20da37db975f09ac2e7772a0694b245076c6d68f85da", - "sha256:18fdc51458abc0a974822333bd3a932d4e06ba2a3243e9a1da305668bd62ec6d", - "sha256:28e8af338240b6f39713a34e337c3813047896ace09d51593d6907c66c0708ba", - "sha256:3835de96524a7b6869a6c710b26c90e94558c31006e96ca3cf6af6751b27dca1", - "sha256:3905c86cc4ab6d71635d6419a6f8d972cab7c634539bba6053c47354fd04452c", - "sha256:3c09d88a07483231119f5017904db8f60ad67906efac3f1baa31b9b7f7cca281", - "sha256:4551728b767f35f86b8e5ec19a363df87450c7376d7419c3cac5b9ceb4bce576", - "sha256:459bbe342c5b2dec5c5223e7c363f291558bc27982ef39ffd6569e8c082bdc83", - "sha256:4f421e3cdd3a273bace013751c345f4ebeef08f05e8c10757533ada360b51a39", - "sha256:577737ec3d4c195c4aef01b757905779a9e9aee608fa1cf0aec16b5576c893d3", - "sha256:57fece29f7cc55d882fe282d9de52f2f522bb85290555b49394102f3621751ee", - "sha256:7976d410e42be9ae7458c1816a416218364e06e162b82e42f7060737e711d9ce", - "sha256:85f568892422a0e96235eb8ea6c5a41c8ccbf55576a2260c0160800dbd7c4f20", - "sha256:8764a78c5464ac6bde91a8c87dd718c27c1cabb7ed2b4beaf36d3e8e390567f9", - "sha256:8935937dad2c9b369c3d932b0edbc52a62647c2afb2fafc0c280f14a8bf56a6a", - "sha256:8fe58d9f6e3d1abf690174fd75800fda9bdc23d2a287e77758dc0e8567e38ce6", - "sha256:937b20955806381e08e54bd9d71f83276d1f883264808521b70b33d98e4dec5d", - "sha256:9569da9e78f0947b249370cb8fadf1015a193c359e7e442ac9ecc585d937f08d", - "sha256:a3b73390511edd2db2d34ff09aa0b2c08be974c71b4c0505b4a048d5dc128c2b", - "sha256:a4eddbe2a715b2dd3849afbdeacf1cc283160b24e09baf64fa5675f51940419d", - "sha256:a5c6dbe09aff091adfa8c7cfc1a0e83fdb8021ddb2c183512775a14f1435fe16", - "sha256:b63e3571b24a7959017573b6455e05b675050bbbea69408f35f3cb984ec54363", - "sha256:bb350eb1060591d8e89d6bac4713d41006cd4d479f5e11db334a48ff8999512f", - "sha256:bf6d987edd4a44dd2fa2723fca2790f9442ae4de2c8438e53fcb1befdf5d823a", - "sha256:bfa6a679410b394600eafd16336b2ce8de43e9b13f7fb9247d84ef5ad2b45e91", - "sha256:c856ec9b42e5af4fe2d8e75970fcc3a2c15925cbcc6e7a9bcb44583b10b95e80", - "sha256:cea56288eeda8b7511d507bbe7790d89ae7049daa5f51ae31a35ae3c05408531", - "sha256:ea212df6e5d3f60341aef46401d32fcfded85593af1d82b8b4a7a68cd67fdd6b", - "sha256:f35567470ee6dbfb946f069ed5f5615b40edcbb5f1e6e1d3d2b114468d505fc6", - "sha256:fbc20975eee093efa2071de80df7f972b7b35e560b213aafabcec7c0bd00bd8c", - "sha256:ff4a8ad9638b7ca52313d8732f37ecd5fd3c8e3aff10a8ccb93176fd5b3812f6" - ], - "version": "==2021.8.3" + "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311", + "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9" + ], + "markers": "python_version >= '3.8'", + "version": "==43.0" }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.26.0" + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.4" }, "requests-toolbelt": { "hashes": [ - "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", - "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" ], - "version": "==0.9.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" }, "rfc3986": { "hashes": [ - "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", - "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" ], - "version": "==1.5.0" + "markers": "python_version >= '3.7'", + "version": "==2.0.0" }, - "secretstorage": { + "rich": { "hashes": [ - "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f", - "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195" + "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", + "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" ], - "markers": "sys_platform == 'linux'", - "version": "==3.3.1" + "markers": "python_full_version >= '3.8.0'", + "version": "==13.9.4" }, - "six": { + "secretstorage": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", + "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "markers": "sys_platform == 'linux'", + "version": "==3.3.3" }, - "smmap": { + "setuptools": { "hashes": [ - "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", - "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" + "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d" ], - "markers": "python_version >= '3.5'", - "version": "==4.0.0" + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==78.1.1" }, "snowballstemmer": { "hashes": [ - "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", - "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" + "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", + "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" ], - "version": "==2.1.0" + "version": "==2.2.0" }, "sphinx": { "hashes": [ - "sha256:3092d929cd807926d846018f2ace47ba2f3b671b309c7a89cd3306e80c826b13", - "sha256:46d52c6cee13fec44744b8c01ed692c18a640f6910a725cbb938bc36e8d64544" + "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3", + "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc" ], "index": "pypi", - "version": "==4.1.2" + "markers": "python_version >= '3.9'", + "version": "==7.3.7" }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", - "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" + "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619", + "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" + "markers": "python_version >= '3.9'", + "version": "==1.0.8" }, "sphinxcontrib-devhelp": { "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" + "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f", + "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" + "markers": "python_version >= '3.9'", + "version": "==1.0.6" }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", - "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" + "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015", + "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" + "markers": "python_version >= '3.9'", + "version": "==2.0.5" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -882,96 +1577,87 @@ }, "sphinxcontrib-qthelp": { "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" + "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6", + "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" + "markers": "python_version >= '3.9'", + "version": "==1.0.7" }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" + "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7", + "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f" ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" + "markers": "python_version >= '3.9'", + "version": "==1.1.10" }, "stevedore": { "hashes": [ - "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", - "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" - ], - "markers": "python_version >= '3.6'", - "version": "==3.3.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d", + "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" + "markers": "python_version >= '3.9'", + "version": "==5.4.0" }, "tomli": { "hashes": [ - "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", - "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], - "markers": "python_version >= '3.6'", - "version": "==1.2.1" + "markers": "python_version < '3.11'", + "version": "==2.0.1" }, - "tqdm": { + "tomlkit": { "hashes": [ - "sha256:07856e19a1fe4d2d9621b539d3f072fa88c9c1ef1f3b7dd4d4953383134c3164", - "sha256:35540feeaca9ac40c304e916729e6b78045cbbeccd3e941b2868f09306798ac9" + "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", + "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.62.1" + "markers": "python_version >= '3.8'", + "version": "==0.13.2" }, "twine": { "hashes": [ - "sha256:087328e9bb405e7ce18527a2dca4042a84c7918658f951110b38bc135acab218", - "sha256:4caec0f1ed78dc4c9b83ad537e453d03ce485725f2aea57f1bb3fdde78dae936" + "sha256:4d74770c88c4fcaf8134d2a6a9d863e40f08255ff7d8e2acb3cbbd57d25f6e9d", + "sha256:fe1d814395bfe50cfbe27783cb74efe93abeac3f66deaeb6c8390e4e92bacb43" ], "index": "pypi", - "version": "==3.4.2" + "markers": "python_version >= '3.8'", + "version": "==5.1.0" }, - "urllib3": { + "typing-extensions": { "hashes": [ - "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", - "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" + "sha256:be199d06d8f09ca2c9425e3aa04a9afba33e892fe079dea959e72df7f8442343", + "sha256:f933a7b288a919ca97adbff656e52ff81f7ff25d98a2aabb9355ca4090f772fe" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", - "version": "==1.26.6" + "markers": "python_version < '3.11'", + "version": "==4.12.0rc1" }, - "webencodings": { + "urllib3": { "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f", + "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1" ], - "version": "==0.5.1" + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.6.0" }, "wheel": { "hashes": [ - "sha256:21014b2bd93c6d0034b6ba5d35e4eb284340e09d63c59aef6fc14b0f346146fd", - "sha256:e2ef7239991699e3355d54f8e968a21bb940a1dbf34a4d226741e64462516fad" + "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85", + "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81" ], "index": "pypi", - "version": "==0.37.0" - }, - "wrapt": { - "hashes": [ - "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" - ], - "version": "==1.12.1" + "markers": "python_version >= '3.8'", + "version": "==0.43.0" }, "zipp": { "hashes": [ - "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3", - "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" + "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091", + "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f" ], - "markers": "python_version >= '3.6'", - "version": "==3.5.0" + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.19.1" } } } diff --git a/clients/python/README.md b/clients/python/README.md index d3ddd73442..8091733992 100644 --- a/clients/python/README.md +++ b/clients/python/README.md @@ -1,6 +1,6 @@ # Tracker Python API Client -The Tracker Python API Client provides a simple Python interface for the [Tracker GraphQL API](https://github.com/canada-ca/tracker/blob/master/api-js/README.md), with the aim of allowing users to easily integrate data from Tracker into existing workflows and platforms. It allows access to the JSON data served by the API without requiring specific knowledge of [GraphQL](https://graphql.org/) or the Tracker API. This is done by providing an object-oriented interface to execute canned queries against the API using [gql](https://github.com/graphql-python/gql). Responses are formatted to remove pagination related structures, and to ensure useful keys are always present. +The Tracker Python API Client provides a simple Python interface for the [Tracker GraphQL API](https://github.com/canada-ca/tracker/blob/master/api/README.md), with the aim of allowing users to easily integrate data from Tracker into existing workflows and platforms. It allows access to the JSON data served by the API without requiring specific knowledge of [GraphQL](https://graphql.org/) or the Tracker API. This is done by providing an object-oriented interface to execute canned queries against the API using [gql](https://github.com/graphql-python/gql). Responses are formatted to remove pagination related structures, and to ensure useful keys are always present. ## Installation @@ -166,7 +166,7 @@ Supposing all your domains, except "foo.bar", properly implement DMARC: ### Build Docs -The client comes with documentation generated by [Sphinx](https://www.sphinx-doc.org/en/3.x/index.html) from the docstrings in the source code. Because of this, they are not included in the Github repo. To build the docs you will first need to install Sphinx. +The client comes with documentation generated by [Sphinx](https://www.sphinx-doc.org/en/3.x/index.html) from the docstrings in the source code. Because of this, they are not included in the GitHub repo. To build the docs you will first need to install Sphinx. The simplest way to ensure you have Sphinx installed is to install the client's dev dependencies with pipenv: diff --git a/clients/python/cloudbuild.yaml b/clients/python/cloudbuild.yaml index e8e7b5b6e6..34ab0b38c5 100644 --- a/clients/python/cloudbuild.yaml +++ b/clients/python/cloudbuild.yaml @@ -1,5 +1,5 @@ steps: - - name: 'gcr.io/track-compliance/ci' + - name: 'northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci' id: install-client-deps dir: clients/python entrypoint: pipenv @@ -7,13 +7,13 @@ steps: env: - PIPENV_NOSPIN=TRUE - - name: 'gcr.io/track-compliance/ci' + - name: 'northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci' id: lint-client dir: clients/python entrypoint: /bin/sh args: ['-c', 'pipenv run black --check tracker_client/ && pipenv run bandit -r tracker_client/'] - - name: 'gcr.io/track-compliance/ci' + - name: 'northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/ci' id: test-client dir: clients/python entrypoint: pipenv @@ -26,4 +26,4 @@ steps: timeout: 1200s options: machineType: 'E2_HIGHCPU_8' - + diff --git a/clients/python/docs/source/index.rst b/clients/python/docs/source/index.rst index 3816e76c8a..65d779de22 100644 --- a/clients/python/docs/source/index.rst +++ b/clients/python/docs/source/index.rst @@ -1,7 +1,7 @@ Welcome to tracker_client's documentation! ========================================== -The Tracker Python API Client provides a simple Python interface for the `Tracker GraphQL API `_, +The Tracker Python API Client provides a simple Python interface for the `Tracker GraphQL API `_, with the aim of allowing users to easily integrate data from Tracker into existing workflows and platforms. diff --git a/clients/python/tests/test_client.py b/clients/python/tests/test_client.py index 812447e297..cef86b7881 100644 --- a/clients/python/tests/test_client.py +++ b/clients/python/tests/test_client.py @@ -66,7 +66,7 @@ def test_client_execute_query_transport_server_error(mocker, capsys): mocker.patch("tracker_client.client.get_auth_token") mocker.patch("tracker_client.client.create_client") test_client = Client() - test_client.gql_client.execute = mocker.MagicMock(side_effect=TransportServerError) + test_client.gql_client.execute = mocker.MagicMock(side_effect=TransportServerError("test error")) with pytest.raises(TransportServerError): test_client.execute_query(None) diff --git a/clients/python/tracker_client/__version__.py b/clients/python/tracker_client/__version__.py index 7018faa661..52ebd469c3 100644 --- a/clients/python/tracker_client/__version__.py +++ b/clients/python/tracker_client/__version__.py @@ -1,2 +1,3 @@ """Single source of truth for versioning""" + __version__ = "1.0.0-alpha.1" diff --git a/clients/python/tracker_client/client.py b/clients/python/tracker_client/client.py index 5904b5230b..0ef5bee5d1 100644 --- a/clients/python/tracker_client/client.py +++ b/clients/python/tracker_client/client.py @@ -1,4 +1,5 @@ """This module defines the Client class, used to connect to the Tracker API.""" + from slugify import slugify from gql.transport.exceptions import ( TransportQueryError, @@ -7,10 +8,10 @@ ) from graphql.error import GraphQLError -from core import create_client, get_auth_token -from domain import Domain -from organization import Organization -import queries +from tracker_client.core import create_client, get_auth_token +from tracker_client.domain import Domain +from tracker_client.organization import Organization +from tracker_client import queries class Client: diff --git a/clients/python/tracker_client/core.py b/clients/python/tracker_client/core.py index 5a5ce417b6..824eeac6b8 100644 --- a/clients/python/tracker_client/core.py +++ b/clients/python/tracker_client/core.py @@ -1,12 +1,13 @@ """This module provides utility functions related to gql, for internal use.""" + import os import re from gql import Client from gql.transport.aiohttp import AIOHTTPTransport -from queries import SIGNIN_MUTATION, TFA_AUTH -from __version__ import __version__ +from tracker_client.queries import SIGNIN_MUTATION, TFA_AUTH +from tracker_client.__version__ import __version__ _JWT_RE = r"^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$" diff --git a/clients/python/tracker_client/domain.py b/clients/python/tracker_client/domain.py index ed81db5836..0ccb5bbf6f 100644 --- a/clients/python/tracker_client/domain.py +++ b/clients/python/tracker_client/domain.py @@ -1,10 +1,11 @@ """This module defines the Domain class, which models domains monitored by Tracker and offers methods to get data about domains.""" + import json -import formatting -import organization as org -import queries +from tracker_client import organization as org +from tracker_client import queries +from tracker_client import formatting class Domain: diff --git a/clients/python/tracker_client/organization.py b/clients/python/tracker_client/organization.py index 100ded4ab0..c5d2cc80cc 100644 --- a/clients/python/tracker_client/organization.py +++ b/clients/python/tracker_client/organization.py @@ -1,12 +1,13 @@ """This module defines the Domain class, which models organizations monitored by Tracker and offers methods to get data about them.""" + import json from slugify import slugify -import domain as dom -from formatting import format_summary -import queries +from tracker_client import domain as dom +from tracker_client import queries +from tracker_client.formatting import format_summary class Organization: @@ -76,17 +77,20 @@ def __str__(self): return self.acronym + " " + self.name def __repr__(self): - return "Organization(client=%r, name=%r, acronym=%r, zone=%r, sector=%r, country=%r, province=%r, city=%r, verified=%r, domain_count=%r)" % ( - self.client, - self.name, - self.acronym, - self.zone, - self.sector, - self.country, - self.province, - self.city, - self.verified, - self.domain_count, + return ( + "Organization(client=%r, name=%r, acronym=%r, zone=%r, sector=%r, country=%r, province=%r, city=%r, verified=%r, domain_count=%r)" + % ( + self.client, + self.name, + self.acronym, + self.zone, + self.sector, + self.country, + self.province, + self.city, + self.verified, + self.domain_count, + ) ) def get_summary(self): diff --git a/database-migration/.dockerignore b/database-migration/.dockerignore new file mode 100644 index 0000000000..fb88459910 --- /dev/null +++ b/database-migration/.dockerignore @@ -0,0 +1,4 @@ +**/.env +node_modules +Dockerfile +README.md diff --git a/database-migration/.env.example b/database-migration/.env.example new file mode 100644 index 0000000000..b30ff0ef2b --- /dev/null +++ b/database-migration/.env.example @@ -0,0 +1,5 @@ +DB_URL= +DB_USER= +DB_PASS= +DB_NAME= +ROOT_PASS= diff --git a/database-migration/.eslintrc b/database-migration/.eslintrc new file mode 100644 index 0000000000..9665f5a604 --- /dev/null +++ b/database-migration/.eslintrc @@ -0,0 +1,24 @@ +{ + "extends": [ + "standard", + "prettier", + "plugin:import/errors", + "plugin:import/warnings" + ], + "plugins": ["jest"], + "env": { + "jest/globals": true + }, + "rules": { + "comma-dangle": ["error", "always-multiline"], + "no-unused-vars": [ + "error", + { + "vars": "all", + "args": "all", + "varsIgnorePattern": "_", + "argsIgnorePattern": "_" + } + ] + } +} diff --git a/scripts/insert-json/.prettierrc b/database-migration/.prettierrc similarity index 100% rename from scripts/insert-json/.prettierrc rename to database-migration/.prettierrc diff --git a/database-migration/Dockerfile b/database-migration/Dockerfile new file mode 100644 index 0000000000..a7dd9c919b --- /dev/null +++ b/database-migration/Dockerfile @@ -0,0 +1,17 @@ +FROM node:25.3.0-alpine + +ENV NODE_ENV production + +WORKDIR /home/node/app + +COPY package.json . +COPY package-lock.json . +COPY .env.example . +COPY index.js . +COPY database.json . + +RUN npm ci + +USER node + +CMD ["npm", "start"] diff --git a/database-migration/README.md b/database-migration/README.md new file mode 100644 index 0000000000..dd338babaf --- /dev/null +++ b/database-migration/README.md @@ -0,0 +1,4 @@ +# Database migration + +This service uses a supplied description to create the database and collections requested. +The idea is for this to be used as an initContainer for Tracker services. diff --git a/database-migration/cloudbuild.yaml b/database-migration/cloudbuild.yaml new file mode 100644 index 0000000000..3d1c791287 --- /dev/null +++ b/database-migration/cloudbuild.yaml @@ -0,0 +1,49 @@ +steps: + - name: node:alpine + id: install + dir: database-migration + entrypoint: npm + args: ['ci', '--no-optional'] + + - name: node:alpine + id: lint + dir: database-migration + entrypoint: npm + args: ['run', lint] + + - name: 'gcr.io/cloud-builders/docker' + id: generate-image-name + entrypoint: 'bash' + dir: database-migration + args: + - '-c' + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/database-migration:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: 'gcr.io/cloud-builders/docker' + id: build + entrypoint: 'bash' + dir: database-migration + args: + - '-c' + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: 'gcr.io/cloud-builders/docker' + id: push-if-master + entrypoint: 'bash' + args: + - '-c' + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi + +timeout: 1200s +options: + machineType: 'E2_HIGHCPU_8' diff --git a/database-migration/database.json b/database-migration/database.json new file mode 100644 index 0000000000..05e7ef27c3 --- /dev/null +++ b/database-migration/database.json @@ -0,0 +1,379 @@ +{ + "type": "database", + "name": "{{dbname}}", + "url": "{{url}}", + "rootPassword": "{{rootPassword}}", + "options": [ + { "type": "user", "username": "{{username}}", "password": "{{password}}" }, + { + "type": "documentcollection", + "name": "users", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "organizations", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "domains", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "selectors", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "auditLogs", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "dns", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "web", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "webScan", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "guidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "dkimGuidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "dmarcGuidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "spfGuidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "httpsGuidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "sslGuidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "chartSummaries", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "dmarcSummaries", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "aggregateGuidanceTags", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "scanSummaryCriteria", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "documentcollection", + "name": "chartSummaryCriteria", + "options": { + "replicationfactor": 3, + "writeconcern": 1, + "numberofshards": 6 + } + }, + { + "type": "documentcollection", + "name": "scanSummaries", + "options": { + "replicationfactor": 3, + "writeconcern": 1, + "numberofshards": 6 + } + }, + { + "type": "documentcollection", + "name": "organizationSummaries", + "options": { + "replicationfactor": 3, + "writeconcern": 1, + "numberofshards": 6 + } + }, + { + "type": "documentcollection", + "name": "additionalFindings", + "options": { + "replicationfactor": 3, + "writeconcern": 1, + "numberofshards": 6 + } + }, + { + "type": "documentcollection", + "name": "tags", + "options": { + "replicationfactor": 3, + "writeconcern": 1, + "numberofshards": 6 + } + }, + { + "type": "edgecollection", + "name": "domainsToSelectors", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "affiliations", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "claims", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "favourites", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "domainsDNS", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "domainsWeb", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "webToWebScans", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "ownership", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "edgecollection", + "name": "domainsToDmarcSummaries", + "options": { + "replicationFactor": 3, + "writeConcern": 1, + "numberOfShards": 6 + } + }, + { + "type": "delimiteranalyzer", + "name": "space-delimiter-analyzer", + "delimiter": " " + }, + { + "type": "searchview", + "name": "domainSearch", + "options": { + "links": { + "domains": { + "fields": { + "domain": { "analyzers": ["space-delimiter-analyzer"] } + } + } + } + } + }, + { + "type": "searchview", + "name": "organizationSearch", + "options": { + "links": { + "organizations": { + "fields": { + "orgDetails": { + "fields": { + "en": { + "fields": { + "acronym": { "analyzers": ["text_en"] }, + "name": { "analyzers": ["text_en"] } + } + }, + "fr": { + "fields": { + "acronym": { "analyzers": ["text_fr"] }, + "name": { "analyzers": ["text_fr"] } + } + } + } + } + } + } + } + } + }, + { + "type": "searchview", + "name": "userSearch", + "options": { + "links": { + "users": { + "fields": { + "displayName": { "analyzers": ["text_en"] }, + "userName": { "analyzers": ["text_en"] } + } + } + } + } + }, + { + "type": "searchview", + "name": "auditLogSearch", + "options": { + "links": { + "auditLogs": { + "fields": { + "initiatedBy": { + "fields": { + "userName": { "analyzers": ["text_en"] } + } + }, + "target": { + "fields": { + "resource": { "analyzers": ["text_en"] } + } + } + } + } + } + } + } + ] +} diff --git a/database-migration/index.js b/database-migration/index.js new file mode 100644 index 0000000000..cb3bd22864 --- /dev/null +++ b/database-migration/index.js @@ -0,0 +1,43 @@ +const { config } = require('dotenv-safe') +const { ensure } = require('arango-tools') +const { Database } = require('arangojs') + +config() + +const { DB_DESCRIPTION = './database.json', DB_NAME, ROOT_PASS, DB_USER, DB_PASS, DB_URL } = process.env + +const schema = require(DB_DESCRIPTION) + +;(async () => { + const systemDatabase = new Database({ url: DB_URL, databaseName: '_system' }) + await systemDatabase.login('root', ROOT_PASS) + const databases = await systemDatabase.listDatabases() + if (!databases.includes(DB_NAME)) { + console.log(`Tracker database ${DB_NAME} does not exist. Creating it.`) + try { + await systemDatabase.createDatabase(DB_NAME, { + users: [ + { + username: DB_USER, + passwd: DB_PASS, + active: true, + }, + ], + }) + } catch (e) { + console.error(`Failed to create database ${DB_NAME}: ${e.message}`) + process.exit(1) + } + } + + await ensure({ + variables: { + rootPassword: ROOT_PASS, + dbname: DB_NAME, + username: DB_USER, + password: DB_PASS, + url: DB_URL, + }, + schema, + }) +})() diff --git a/database-migration/package-lock.json b/database-migration/package-lock.json new file mode 100644 index 0000000000..5a3668bf91 --- /dev/null +++ b/database-migration/package-lock.json @@ -0,0 +1,4136 @@ +{ + "name": "database-setup", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "database-setup", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "arango-tools": "^0.6.0", + "arangojs": "^9.2.0", + "dotenv-safe": "^8.2.0", + "json-placeholder-replacer": "^1.0.35" + }, + "devDependencies": { + "eslint": "^8.30.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-standard": "^5.0.0", + "prettier": "^2.6.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arango-tools": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", + "integrity": "sha512-2XjPPddz7Vc07JrOyXFYGzXI+QZOAR+I0kiyklKBevWVTTDh1lYRetZh3X1fN+smibgL2DY3jKwHK14aSzVkSw==", + "dependencies": { + "arangojs": "^7.2.0", + "assign-deep": "^1.0.1", + "json-placeholder-replacer": "^1.0.35" + } + }, + "node_modules/arango-tools/node_modules/arangojs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.8.0.tgz", + "integrity": "sha512-aJFlMKlVr4sIO5GDMuykBVNVxWeZTkWDgYbbl9cIuxVctp8Lhs6dW5fr5MYlwAndnOEyi3bvbrhZIucly2IpWQ==", + "dependencies": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arango-tools/node_modules/file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/arango-tools/node_modules/mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "dependencies": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/arango-tools/node_modules/multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "dependencies": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/arangojs": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-9.2.0.tgz", + "integrity": "sha512-WIoFPCrBeuX1kBzGwue4t+jjYFW/ipto4ZO5s6e1v6dMsZE4I+d5Bid2glL+D5UeUJ6qGgqfe3KOAAW1t0Pc2A==", + "dependencies": { + "@types/node": "^20.11.26" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "dependencies": { + "assign-symbols": "^2.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-safe": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", + "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/es-abstract": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-n": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.6.0.tgz", + "integrity": "sha512-Hd/F7wz4Mj44Jp0H6Jtty13NcE69GNTY0rVlgTIj1XBnGGVI6UTdDrpE6vqu3AHo07bygq/N+7OH/lgz1emUJw==", + "dev": true, + "peer": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-standard": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz", + "integrity": "sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg==", + "deprecated": "standard 16.0.0 and eslint-config-standard 16.0.0 no longer require the eslint-plugin-standard package. You can remove it from your dependencies with 'npm rm eslint-plugin-standard'. More info here: https://github.com/standard/standard/issues/1316", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-placeholder-replacer": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", + "integrity": "sha512-edlSWqcFVUpKPshaIcJfXpQ8eu0//gk8iU6XHWkCZIp5QEp4hoCFR7uk+LrIzhLTSqmBQ9VBs+EYK8pvWGEpRg==", + "bin": { + "jpr": "dist/index.js", + "json-placeholder-replacer": "dist/index.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-document": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==", + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==", + "engines": { + "node": ">=10" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@eslint/eslintrc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "requires": { + "undici-types": "~6.21.0" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "arango-tools": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", + "integrity": "sha512-2XjPPddz7Vc07JrOyXFYGzXI+QZOAR+I0kiyklKBevWVTTDh1lYRetZh3X1fN+smibgL2DY3jKwHK14aSzVkSw==", + "requires": { + "arangojs": "^7.2.0", + "assign-deep": "^1.0.1", + "json-placeholder-replacer": "^1.0.35" + }, + "dependencies": { + "arangojs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.8.0.tgz", + "integrity": "sha512-aJFlMKlVr4sIO5GDMuykBVNVxWeZTkWDgYbbl9cIuxVctp8Lhs6dW5fr5MYlwAndnOEyi3bvbrhZIucly2IpWQ==", + "requires": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + } + }, + "file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==" + }, + "mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "requires": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + } + }, + "multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "requires": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + } + } + } + }, + "arangojs": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-9.2.0.tgz", + "integrity": "sha512-WIoFPCrBeuX1kBzGwue4t+jjYFW/ipto4ZO5s6e1v6dMsZE4I+d5Bid2glL+D5UeUJ6qGgqfe3KOAAW1t0Pc2A==", + "requires": { + "@types/node": "^20.11.26" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "requires": { + "assign-symbols": "^2.0.2" + } + }, + "assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "peer": true, + "requires": { + "semver": "^7.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, + "dotenv-safe": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", + "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", + "requires": { + "dotenv": "^8.2.0" + } + }, + "es-abstract": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "unbox-primitive": "^1.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.4.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "peer": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "peer": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "peer": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-n": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.6.0.tgz", + "integrity": "sha512-Hd/F7wz4Mj44Jp0H6Jtty13NcE69GNTY0rVlgTIj1XBnGGVI6UTdDrpE6vqu3AHo07bygq/N+7OH/lgz1emUJw==", + "dev": true, + "peer": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "requires": {} + }, + "eslint-plugin-standard": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz", + "integrity": "sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-placeholder-replacer": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", + "integrity": "sha512-edlSWqcFVUpKPshaIcJfXpQ8eu0//gk8iU6XHWkCZIp5QEp4hoCFR7uk+LrIzhLTSqmBQ9VBs+EYK8pvWGEpRg==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "min-document": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "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.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "word-wrap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==" + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/database-migration/package.json b/database-migration/package.json new file mode 100644 index 0000000000..47fbdedad3 --- /dev/null +++ b/database-migration/package.json @@ -0,0 +1,29 @@ +{ + "name": "database-setup", + "version": "1.0.0", + "description": "creates the database according to a setup file", + "main": "index.js", + "scripts": { + "lint": "eslint", + "start": "node --dns-result-order=ipv4first --experimental-json-modules --experimental-vm-modules index.js" + }, + "keywords": [], + "author": "Mike Williamson", + "license": "MIT", + "dependencies": { + "arango-tools": "^0.6.0", + "arangojs": "^9.2.0", + "dotenv-safe": "^8.2.0", + "json-placeholder-replacer": "^1.0.35" + }, + "devDependencies": { + "eslint": "^8.30.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-standard": "^5.0.0", + "prettier": "^2.6.0" + } +} diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index 83a60b6a12..0000000000 --- a/deploy/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Deploy - -The Tracker project uses [Flux](https://fluxcd.io/), to enable [pull based](https://alex.kaskaso.li/post/pull-based-pipelines) Continuous Deployment. - -The directory layout here to allow the patching of a basic flux install using [Kustomize](https://kustomize.io/). - -## Testing config changes - -Config changes can be pretty high impact, so trying it out somewhere is pretty useful. To that end, we have a few ways to bring up a "non-prod" version of the app; basically, using a self signed cert and requesting its own IP address. - -Containerized applications [read their config from the environment](https://12factor.net/config), and that environment is largely populated via secrets. Consequently we create these secrets and the namespaces they live in before doing the deployment. - -You can run `make credentials` in the project root to generate a basic set of dev credentials. Without passing any arguments, `make credentials` is equivalent to `make credentials mode=dev displayname=admin email=admin@example.com password=admin`. These default arguments set the credentials for the super admin user, and if you intend to log into your testing instance, make a note of those or adjust the arguments as needed. - -In each of the cases below, `make deploy` installs flux into whatever cluster `kubectl` is currently pointing at. Flux will clone the Tracker repository and start applying the config it finds within to create a fully working instance of Tracker. This can take several minutes. - -### Testing in Minikube - -```bash -make secrets env=minikube -make deploy env=minikube -``` - -### Testing on GKE - -```bash -make secrets env=test -make deploy env=test -``` - -### Testing on AKS - -```bash -make secrets env=aks -make deploy env=aks -``` - -## Deploying to Prod - -Deploying to prod is a little anticlimactic. You'll want some read/write credentials for Flux so that it can [update our config](https://toolkit.fluxcd.io/components/image/imageupdateautomations/#update-strategy) with new image tags, but everything else is the same. - -Tracker uses SSH deploy keys to allow those updates, and uses kustomize to [generate secrets](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/secretGeneratorPlugin.md#secret-values-from-local-files). To create the keys, use the following commands. - -```bash -ssh-keygen -q -N "" -C "flux-read-write" -f ./deploy/creds/readwrite/identity -ssh-keyscan github.com > ./deploy/creds/readwrite/known_hosts -``` - -[Add the new deploy key](https://github.com/canada-ca/tracker/settings/keys/new) to the Tracker repo, and select "Allow write access". -After that it's basically the same: - -```bash -make secrets env=gke -make deploy env=gke -``` - -## Updating Flux - -Update Flux [as you would normally](https://fluxcd.io/docs/installation/), and then run `make update-flux`, to update the config. diff --git a/deploy/aks/app-kustomization.yaml b/deploy/aks/app-kustomization.yaml deleted file mode 100644 index 19ac82c25c..0000000000 --- a/deploy/aks/app-kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: app - namespace: flux-system -spec: - path: ./app/aks - diff --git a/deploy/aks/kustomization.yaml b/deploy/aks/kustomization.yaml deleted file mode 100644 index 3669131f60..0000000000 --- a/deploy/aks/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -patchesStrategicMerge: -- app-kustomization.yaml -- platform-kustomization.yaml diff --git a/deploy/aks/platform-kustomization.yaml b/deploy/aks/platform-kustomization.yaml deleted file mode 100644 index d69d19b3ed..0000000000 --- a/deploy/aks/platform-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: platform - namespace: flux-system -spec: - path: ./platform/aks diff --git a/deploy/bases/api-registry.yaml b/deploy/bases/api-registry.yaml deleted file mode 100644 index 0e2b4ea963..0000000000 --- a/deploy/bases/api-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: api - namespace: flux-system -spec: - image: gcr.io/track-compliance/api-js - interval: 1m0s diff --git a/deploy/bases/app-kustomization.yaml b/deploy/bases/app-kustomization.yaml deleted file mode 100644 index ad6eb051c7..0000000000 --- a/deploy/bases/app-kustomization.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: app - namespace: flux-system -spec: - interval: 2m0s - path: ./app/gke - prune: true - sourceRef: - kind: GitRepository - name: tracker - timeout: 2m0s - dependsOn: - - name: platform - diff --git a/deploy/bases/autoscan-registry.yaml b/deploy/bases/autoscan-registry.yaml deleted file mode 100644 index 5c0c0e140c..0000000000 --- a/deploy/bases/autoscan-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: autoscan - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/autoscan - interval: 1m0s diff --git a/deploy/bases/dmarc-report-registry.yaml b/deploy/bases/dmarc-report-registry.yaml deleted file mode 100644 index a03a3ebfd3..0000000000 --- a/deploy/bases/dmarc-report-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: dmarc-report - namespace: flux-system -spec: - image: gcr.io/track-compliance/dmarc-report - interval: 1m0s diff --git a/deploy/bases/dns-scanner-registry.yaml b/deploy/bases/dns-scanner-registry.yaml deleted file mode 100644 index 752b187108..0000000000 --- a/deploy/bases/dns-scanner-registry.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: dns-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/scanners/dns - interval: 1m0s - diff --git a/deploy/bases/flux.yaml b/deploy/bases/flux.yaml deleted file mode 100644 index df65be874b..0000000000 --- a/deploy/bases/flux.yaml +++ /dev/null @@ -1,3379 +0,0 @@ ---- -# Flux version: v0.13.4 -# Components: source-controller,kustomize-controller,notification-controller,image-reflector-controller,image-automation-controller -apiVersion: v1 -kind: Namespace -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: flux-system ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: alerts.notification.toolkit.fluxcd.io -spec: - group: notification.toolkit.fluxcd.io - names: - kind: Alert - listKind: AlertList - plural: alerts - singular: alert - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Alert is the Schema for the alerts API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: AlertSpec defines an alerting rule for events involving a list of objects - properties: - eventSeverity: - default: info - description: Filter events based on severity, defaults to ('info'). If set to 'info' no events will be filtered. - enum: - - info - - error - type: string - eventSources: - description: Filter events based on the involved objects. - items: - description: CrossNamespaceObjectReference contains enough information to let you locate the typed referenced object at cluster level - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - Bucket - - GitRepository - - Kustomization - - HelmRelease - - HelmChart - - HelmRepository - - ImageRepository - - ImagePolicy - - ImageUpdateAutomation - type: string - name: - description: Name of the referent - maxLength: 53 - minLength: 1 - type: string - namespace: - description: Namespace of the referent - maxLength: 53 - minLength: 1 - type: string - required: - - name - type: object - type: array - exclusionList: - description: A list of Golang regular expressions to be used for excluding messages. - items: - type: string - type: array - providerRef: - description: Send events using this provider. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - summary: - description: Short description of the impact and affected cluster. - type: string - suspend: - description: This flag tells the controller to suspend subsequent events dispatching. Defaults to false. - type: boolean - required: - - eventSources - - providerRef - type: object - status: - description: AlertStatus defines the observed state of Alert - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: buckets.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: Bucket - listKind: BucketList - plural: buckets - singular: bucket - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: BucketSpec defines the desired state of an S3 compatible bucket - properties: - bucketName: - description: The bucket name. - type: string - endpoint: - description: The bucket endpoint address. - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the .sourceignore format (which is the same as .gitignore). If not provided, a default will be used, consult the documentation for your version to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS S3 HTTP endpoint. - type: boolean - interval: - description: The interval at which to check for bucket updates. - type: string - provider: - default: generic - description: The S3 compatible storage provider name, default ('generic'). - enum: - - generic - - aws - type: string - region: - description: The bucket region. - type: string - secretRef: - description: The name of the secret containing authentication credentials for the Bucket. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation of this source. - type: boolean - timeout: - default: 20s - description: The timeout for download operations, defaults to 20s. - type: string - required: - - bucketName - - endpoint - - interval - type: object - status: - description: BucketStatus defines the observed state of a bucket - properties: - artifact: - description: Artifact represents the output of the last successful Bucket sync. - properties: - checksum: - description: Checksum is the SHA1 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable in the origin source system. It can be a Git commit SHA, Git tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the last Bucket sync. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: gitrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: GitRepository - listKind: GitRepositoryList - plural: gitrepositories - shortNames: - - gitrepo - singular: gitrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GitRepositorySpec defines the desired state of a Git repository. - properties: - gitImplementation: - default: go-git - description: Determines which git client library to use. Defaults to go-git, valid values are ('go-git', 'libgit2'). - enum: - - go-git - - libgit2 - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the .sourceignore format (which is the same as .gitignore). If not provided, a default will be used, consult the documentation for your version to find out what those are. - type: string - interval: - description: The interval at which to check for repository updates. - type: string - recurseSubmodules: - description: When enabled, after the clone is created, initializes all submodules within, using their default settings. This option is available only when using the 'go-git' GitImplementation. - type: boolean - ref: - description: The Git reference to checkout and monitor for changes, defaults to master branch. - properties: - branch: - default: master - description: The Git branch to checkout, defaults to master. - type: string - commit: - description: The Git commit SHA to checkout, if specified Tag filters will be ignored. - type: string - semver: - description: The Git tag semver expression, takes precedence over Tag. - type: string - tag: - description: The Git tag to checkout, takes precedence over Branch. - type: string - type: object - secretRef: - description: The secret name containing the Git credentials. For HTTPS repositories the secret must contain username and password fields. For SSH repositories the secret must contain identity, identity.pub and known_hosts fields. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation of this source. - type: boolean - timeout: - default: 20s - description: The timeout for remote Git operations like cloning, defaults to 20s. - type: string - url: - description: The repository URL, can be a HTTP/S or SSH address. - pattern: ^(http|https|ssh):// - type: string - verify: - description: Verify OpenPGP signature for the Git commit HEAD points to. - properties: - mode: - description: Mode describes what git object should be verified, currently ('head'). - enum: - - head - type: string - secretRef: - description: The secret name containing the public keys of all trusted Git authors. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - required: - - mode - type: object - required: - - interval - - url - type: object - status: - description: GitRepositoryStatus defines the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the output of the last successful repository sync. - properties: - checksum: - description: Checksum is the SHA1 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable in the origin source system. It can be a Git commit SHA, Git tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the last repository sync. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: helmcharts.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmChart - listKind: HelmChartList - plural: helmcharts - shortNames: - - hc - singular: helmchart - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmChartSpec defines the desired state of a Helm chart. - properties: - chart: - description: The name or path the Helm chart is available at in the SourceRef. - type: string - interval: - description: The interval at which to check the Source for updates. - type: string - sourceRef: - description: The reference to the Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent, valid values are ('HelmRepository', 'GitRepository', 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation of this source. - type: boolean - valuesFile: - description: Alternative values file to use as the default chart values, expected to be a relative path in the SourceRef. Deprecated in favor of ValuesFiles, for backwards compatibility the file defined here is merged before the ValuesFiles items. Ignored when omitted. - type: string - valuesFiles: - description: Alternative list of values files to use as the chart values (values.yaml is not included by default), expected to be a relative path in the SourceRef. Values files are merged in the order of this list with the last file overriding the first. Ignored when omitted. - items: - type: string - type: array - version: - default: '*' - description: The chart version semver expression, ignored for charts from GitRepository and Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - description: HelmChartStatus defines the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful chart sync. - properties: - checksum: - description: Checksum is the SHA1 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable in the origin source system. It can be a Git commit SHA, Git tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the last chart pulled. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: helmrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmRepository - listKind: HelmRepositoryList - plural: helmrepositories - shortNames: - - helmrepo - singular: helmrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmRepositorySpec defines the reference to a Helm repository. - properties: - interval: - description: The interval at which to check the upstream for updates. - type: string - secretRef: - description: The name of the secret containing authentication credentials for the Helm repository. For HTTP/S basic auth the secret must contain username and password fields. For TLS the secret must contain a certFile and keyFile, and/or caCert fields. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation of this source. - type: boolean - timeout: - default: 60s - description: The timeout of index downloading, defaults to 60s. - type: string - url: - description: The Helm repository URL, a valid URL contains at least a protocol and host. - type: string - required: - - interval - - url - type: object - status: - description: HelmRepositoryStatus defines the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the output of the last successful repository sync. - properties: - checksum: - description: Checksum is the SHA1 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable in the origin source system. It can be a Git commit SHA, Git tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the last index fetched. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: imagepolicies.image.toolkit.fluxcd.io -spec: - group: image.toolkit.fluxcd.io - names: - kind: ImagePolicy - listKind: ImagePolicyList - plural: imagepolicies - singular: imagepolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.latestImage - name: LatestImage - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ImagePolicy is the Schema for the imagepolicies API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ImagePolicySpec defines the parameters for calculating the ImagePolicy - properties: - filterTags: - description: FilterTags enables filtering for only a subset of tags based on a set of rules. If no rules are provided, all the tags from the repository will be ordered and compared. - properties: - extract: - description: Extract allows a capture group to be extracted from the specified regular expression pattern, useful before tag evaluation. - type: string - pattern: - description: Pattern specifies a regular expression pattern used to filter for image tags. - type: string - type: object - imageRepositoryRef: - description: ImageRepositoryRef points at the object specifying the image being scanned - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - policy: - description: Policy gives the particulars of the policy to be followed in selecting the most recent image - properties: - alphabetical: - description: Alphabetical set of rules to use for alphabetical ordering of the tags. - properties: - order: - default: asc - description: Order specifies the sorting order of the tags. Given the letters of the alphabet as tags, ascending order would select Z, and descending order would select A. - enum: - - asc - - desc - type: string - type: object - numerical: - description: Numerical set of rules to use for numerical ordering of the tags. - properties: - order: - default: asc - description: Order specifies the sorting order of the tags. Given the integer values from 0 to 9 as tags, ascending order would select 9, and descending order would select 0. - enum: - - asc - - desc - type: string - type: object - semver: - description: SemVer gives a semantic version range to check against the tags available. - properties: - range: - description: Range gives a semver range for the image tag; the highest version within the range that's a tag yields the latest image. - type: string - required: - - range - type: object - type: object - required: - - imageRepositoryRef - - policy - type: object - status: - description: ImagePolicyStatus defines the observed state of ImagePolicy - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - latestImage: - description: LatestImage gives the first in the list of images scanned by the image repository, when filtered and ordered according to the policy. - type: string - observedGeneration: - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.latestImage - name: LatestImage - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: ImagePolicy is the Schema for the imagepolicies API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ImagePolicySpec defines the parameters for calculating the ImagePolicy - properties: - filterTags: - description: FilterTags enables filtering for only a subset of tags based on a set of rules. If no rules are provided, all the tags from the repository will be ordered and compared. - properties: - extract: - description: Extract allows a capture group to be extracted from the specified regular expression pattern, useful before tag evaluation. - type: string - pattern: - description: Pattern specifies a regular expression pattern used to filter for image tags. - type: string - type: object - imageRepositoryRef: - description: ImageRepositoryRef points at the object specifying the image being scanned - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - policy: - description: Policy gives the particulars of the policy to be followed in selecting the most recent image - properties: - alphabetical: - description: Alphabetical set of rules to use for alphabetical ordering of the tags. - properties: - order: - default: asc - description: Order specifies the sorting order of the tags. Given the letters of the alphabet as tags, ascending order would select Z, and descending order would select A. - enum: - - asc - - desc - type: string - type: object - numerical: - description: Numerical set of rules to use for numerical ordering of the tags. - properties: - order: - default: asc - description: Order specifies the sorting order of the tags. Given the integer values from 0 to 9 as tags, ascending order would select 9, and descending order would select 0. - enum: - - asc - - desc - type: string - type: object - semver: - description: SemVer gives a semantic version range to check against the tags available. - properties: - range: - description: Range gives a semver range for the image tag; the highest version within the range that's a tag yields the latest image. - type: string - required: - - range - type: object - type: object - required: - - imageRepositoryRef - - policy - type: object - status: - description: ImagePolicyStatus defines the observed state of ImagePolicy - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - latestImage: - description: LatestImage gives the first in the list of images scanned by the image repository, when filtered and ordered according to the policy. - type: string - observedGeneration: - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: imagerepositories.image.toolkit.fluxcd.io -spec: - group: image.toolkit.fluxcd.io - names: - kind: ImageRepository - listKind: ImageRepositoryList - plural: imagerepositories - singular: imagerepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.lastScanResult.scanTime - name: Last scan - type: string - - jsonPath: .status.lastScanResult.tagCount - name: Tags - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ImageRepository is the Schema for the imagerepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ImageRepositorySpec defines the parameters for scanning an image repository, e.g., `fluxcd/flux`. - properties: - certSecretRef: - description: "CertSecretRef can be given the name of a secret containing either or both of \n - a PEM-encoded client certificate (`certFile`) and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`) \n and whichever are supplied, will be used for connecting to the registry. The client cert and key are useful if you are authenticating with a certificate; the CA cert is useful if you are using a self-signed server certificate." - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - image: - description: Image is the name of the image repository - type: string - interval: - description: Interval is the length of time to wait between scans of the image repository. - type: string - secretRef: - description: SecretRef can be given the name of a secret containing credentials to use for the image registry. The secret should be created with `kubectl create secret docker-registry`, or the equivalent. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent image scans. It does not apply to already started scans. Defaults to false. - type: boolean - timeout: - description: Timeout for image scanning. Defaults to 'Interval' duration. - type: string - type: object - status: - description: ImageRepositoryStatus defines the observed state of ImageRepository - properties: - canonicalImageName: - description: CanonicalName is the name of the image repository with all the implied bits made explicit; e.g., `docker.io/library/alpine` rather than `alpine`. - type: string - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - lastScanResult: - description: LastScanResult contains the number of fetched tags. - properties: - scanTime: - format: date-time - type: string - tagCount: - type: integer - required: - - tagCount - type: object - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.lastScanResult.scanTime - name: Last scan - type: string - - jsonPath: .status.lastScanResult.tagCount - name: Tags - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: ImageRepository is the Schema for the imagerepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ImageRepositorySpec defines the parameters for scanning an image repository, e.g., `fluxcd/flux`. - properties: - certSecretRef: - description: "CertSecretRef can be given the name of a secret containing either or both of \n - a PEM-encoded client certificate (`certFile`) and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`) \n and whichever are supplied, will be used for connecting to the registry. The client cert and key are useful if you are authenticating with a certificate; the CA cert is useful if you are using a self-signed server certificate." - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - image: - description: Image is the name of the image repository - type: string - interval: - description: Interval is the length of time to wait between scans of the image repository. - type: string - secretRef: - description: SecretRef can be given the name of a secret containing credentials to use for the image registry. The secret should be created with `kubectl create secret docker-registry`, or the equivalent. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent image scans. It does not apply to already started scans. Defaults to false. - type: boolean - timeout: - description: Timeout for image scanning. Defaults to 'Interval' duration. - type: string - type: object - status: - description: ImageRepositoryStatus defines the observed state of ImageRepository - properties: - canonicalImageName: - description: CanonicalName is the name of the image repository with all the implied bits made explicit; e.g., `docker.io/library/alpine` rather than `alpine`. - type: string - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - lastScanResult: - description: LastScanResult contains the number of fetched tags. - properties: - scanTime: - format: date-time - type: string - tagCount: - type: integer - required: - - tagCount - type: object - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: imageupdateautomations.image.toolkit.fluxcd.io -spec: - group: image.toolkit.fluxcd.io - names: - kind: ImageUpdateAutomation - listKind: ImageUpdateAutomationList - plural: imageupdateautomations - singular: imageupdateautomation - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.lastAutomationRunTime - name: Last run - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ImageUpdateAutomation is the Schema for the imageupdateautomations API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation - properties: - checkout: - description: Checkout gives the parameters for cloning the git repository, ready to make changes. - properties: - branch: - description: Branch gives the branch to clone from the git repository. If `.spec.push` is not supplied, commits will also be pushed to this branch. - type: string - gitRepositoryRef: - description: GitRepositoryRef refers to the resource giving access details to a git repository to update files in. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - required: - - branch - - gitRepositoryRef - type: object - commit: - description: Commit specifies how to commit to the git repository. - properties: - authorEmail: - description: AuthorEmail gives the email to provide when making a commit - type: string - authorName: - description: AuthorName gives the name to provide when making a commit - type: string - messageTemplate: - description: MessageTemplate provides a template for the commit message, into which will be interpolated the details of the change made. - type: string - signingKey: - description: SigningKey provides the option to sign commits with a GPG key - properties: - secretRef: - description: SecretRef holds the name to a secret that contains a 'git.asc' key corresponding to the ASCII Armored file containing the GPG signing keypair as the value. It must be in the same namespace as the ImageUpdateAutomation. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - type: object - required: - - authorEmail - - authorName - type: object - interval: - description: Interval gives an lower bound for how often the automation run should be attempted. - type: string - push: - description: Push specifies how and where to push commits made by the automation. If missing, commits are pushed (back) to `.spec.checkout.branch`. - properties: - branch: - description: Branch specifies that commits should be pushed to the branch named. The branch is created using `.spec.checkout.branch` as the starting point, if it doesn't already exist. - type: string - required: - - branch - type: object - suspend: - description: Suspend tells the controller to not run this automation, until it is unset (or set to false). Defaults to false. - type: boolean - update: - default: - strategy: Setters - description: Update gives the specification for how to update the files in the repository. This can be left empty, to use the default value. - properties: - path: - description: Path to the directory containing the manifests to be updated. Defaults to 'None', which translates to the root path of the GitRepositoryRef. - type: string - strategy: - default: Setters - description: Strategy names the strategy to be used. - enum: - - Setters - type: string - required: - - strategy - type: object - required: - - checkout - - commit - - interval - type: object - status: - description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastAutomationRunTime: - description: LastAutomationRunTime records the last time the controller ran this automation through to completion (even if no updates were made). - format: date-time - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - lastPushCommit: - description: LastPushCommit records the SHA1 of the last commit made by the controller, for this automation object - type: string - lastPushTime: - description: LastPushTime records the time of the last pushed change. - format: date-time - type: string - observedGeneration: - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.lastAutomationRunTime - name: Last run - type: string - name: v1alpha2 - schema: - openAPIV3Schema: - description: ImageUpdateAutomation is the Schema for the imageupdateautomations API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation - properties: - git: - description: GitSpec contains all the git-specific definitions. This is technically optional, but in practice mandatory until there are other kinds of source allowed. - properties: - checkout: - description: Checkout gives the parameters for cloning the git repository, ready to make changes. If not present, the `spec.ref` field from the referenced `GitRepository` or its default will be used. - properties: - ref: - description: Reference gives a branch, tag or commit to clone from the Git repository. - properties: - branch: - default: master - description: The Git branch to checkout, defaults to master. - type: string - commit: - description: The Git commit SHA to checkout, if specified Tag filters will be ignored. - type: string - semver: - description: The Git tag semver expression, takes precedence over Tag. - type: string - tag: - description: The Git tag to checkout, takes precedence over Branch. - type: string - type: object - required: - - ref - type: object - commit: - description: Commit specifies how to commit to the git repository. - properties: - author: - description: Author gives the email and optionally the name to use as the author of commits. - properties: - email: - description: Email gives the email to provide when making a commit. - type: string - name: - description: Name gives the name to provide when making a commit. - type: string - required: - - email - type: object - messageTemplate: - description: MessageTemplate provides a template for the commit message, into which will be interpolated the details of the change made. - type: string - signingKey: - description: SigningKey provides the option to sign commits with a GPG key - properties: - secretRef: - description: SecretRef holds the name to a secret that contains a 'git.asc' key corresponding to the ASCII Armored file containing the GPG signing keypair as the value. It must be in the same namespace as the ImageUpdateAutomation. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - type: object - required: - - author - type: object - push: - description: Push specifies how and where to push commits made by the automation. If missing, commits are pushed (back) to `.spec.checkout.branch` or its default. - properties: - branch: - description: Branch specifies that commits should be pushed to the branch named. The branch is created using `.spec.checkout.branch` as the starting point, if it doesn't already exist. - type: string - required: - - branch - type: object - required: - - commit - type: object - interval: - description: Interval gives an lower bound for how often the automation run should be attempted. - type: string - sourceRef: - description: SourceRef refers to the resource giving access details to a git repository. - properties: - apiVersion: - description: API version of the referent - type: string - kind: - default: GitRepository - description: Kind of the referent - enum: - - GitRepository - type: string - name: - description: Name of the referent - type: string - required: - - kind - - name - type: object - suspend: - description: Suspend tells the controller to not run this automation, until it is unset (or set to false). Defaults to false. - type: boolean - update: - default: - strategy: Setters - description: Update gives the specification for how to update the files in the repository. This can be left empty, to use the default value. - properties: - path: - description: Path to the directory containing the manifests to be updated. Defaults to 'None', which translates to the root path of the GitRepositoryRef. - type: string - strategy: - default: Setters - description: Strategy names the strategy to be used. - enum: - - Setters - type: string - required: - - strategy - type: object - required: - - interval - - sourceRef - type: object - status: - description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastAutomationRunTime: - description: LastAutomationRunTime records the last time the controller ran this automation through to completion (even if no updates were made). - format: date-time - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - lastPushCommit: - description: LastPushCommit records the SHA1 of the last commit made by the controller, for this automation object - type: string - lastPushTime: - description: LastPushTime records the time of the last pushed change. - format: date-time - type: string - observedGeneration: - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: kustomizations.kustomize.toolkit.fluxcd.io -spec: - group: kustomize.toolkit.fluxcd.io - names: - kind: Kustomization - listKind: KustomizationList - plural: kustomizations - shortNames: - - ks - singular: kustomization - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Kustomization is the Schema for the kustomizations API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: KustomizationSpec defines the desired state of a kustomization. - properties: - decryption: - description: Decrypt Kubernetes secrets before applying them on the cluster. - properties: - provider: - description: Provider is the name of the decryption engine. - enum: - - sops - type: string - secretRef: - description: The secret name containing the private OpenPGP keys used for decryption. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - required: - - provider - type: object - dependsOn: - description: DependsOn may contain a dependency.CrossNamespaceDependencyReference slice with references to Kustomization resources that must be ready before this Kustomization can be reconciled. - items: - description: CrossNamespaceDependencyReference holds the reference to a dependency. - properties: - name: - description: Name holds the name reference of a dependency. - type: string - namespace: - description: Namespace holds the namespace reference of a dependency. - type: string - required: - - name - type: object - type: array - force: - default: false - description: Force instructs the controller to recreate resources when patching fails due to an immutable field change. - type: boolean - healthChecks: - description: A list of resources to be included in the health assessment. - items: - description: NamespacedObjectKindReference contains enough information to let you locate the typed referenced object in any namespace - properties: - apiVersion: - description: API version of the referent, if not specified the Kubernetes preferred version will be used - type: string - kind: - description: Kind of the referent - type: string - name: - description: Name of the referent - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference - type: string - required: - - kind - - name - type: object - type: array - images: - description: Images is a list of (image name, new name, new tag or digest) for changing image names, tags or digests. This can also be achieved with a patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. - properties: - digest: - description: Digest is the value used to replace the original image tag. If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original name. - type: string - newTag: - description: NewTag is the value used to replace the original tag. - type: string - required: - - name - type: object - type: array - interval: - description: The interval at which to reconcile the Kustomization. - type: string - kubeConfig: - description: The KubeConfig for reconciling the Kustomization on a remote cluster. When specified, KubeConfig takes precedence over ServiceAccountName. - properties: - secretRef: - description: SecretRef holds the name to a secret that contains a 'value' key with the kubeconfig file as the value. It must be in the same namespace as the Kustomization. It is recommended that the kubeconfig is self-contained, and the secret is regularly updated if credentials such as a cloud-access-token expire. Cloud specific `cmd-path` auth helpers will not function without adding binaries and credentials to the Pod that is responsible for reconciling the Kustomization. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - type: object - patchesJson6902: - description: JSON 6902 patches, defined as inline YAML objects. - items: - description: JSON6902Patch contains a JSON6902 patch and the target the patch should be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with an array of operation objects. - items: - description: JSON6902 is a JSON6902 operation object. https://tools.ietf.org/html/rfc6902#section-4 - properties: - from: - type: string - op: - enum: - - test - - remove - - add - - replace - - move - - copy - type: string - path: - type: string - value: - x-kubernetes-preserve-unknown-fields: true - required: - - op - - path - type: object - type: array - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: AnnotationSelector is a string that follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. - type: string - group: - description: Group is the API group to select resources from. Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select resources from. Together with Group and Version it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: Version of the API Group to select resources from. Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - - target - type: object - type: array - patchesStrategicMerge: - description: Strategic merge patches, defined as inline YAML objects. - items: - x-kubernetes-preserve-unknown-fields: true - type: array - path: - description: Path to the directory containing the kustomization.yaml file, or the set of plain YAMLs a kustomization.yaml should be generated for. Defaults to 'None', which translates to the root path of the SourceRef. - type: string - postBuild: - description: PostBuild describes which actions to perform on the YAML manifest generated by building the kustomize overlay. - properties: - substitute: - additionalProperties: - type: string - description: Substitute holds a map of key/value pairs. The variables defined in your YAML manifests that match any of the keys defined in the map will be substituted with the set value. Includes support for bash string replacement functions e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}. - type: object - substituteFrom: - description: SubstituteFrom holds references to ConfigMaps and Secrets containing the variables and their values to be substituted in the YAML manifests. The ConfigMap and the Secret data keys represent the var names and they must match the vars declared in the manifests for the substitution to happen. - items: - description: SubstituteReference contains a reference to a resource containing the variables name and value. - properties: - kind: - description: Kind of the values referent, valid values are ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: Name of the values referent. Should reside in the same namespace as the referring resource. - maxLength: 253 - minLength: 1 - type: string - required: - - kind - - name - type: object - type: array - type: object - prune: - description: Prune enables garbage collection. - type: boolean - retryInterval: - description: The interval at which to retry a previously failed reconciliation. When not specified, the controller uses the KustomizationSpec.Interval value to retry failures. - type: string - serviceAccountName: - description: The name of the Kubernetes service account to impersonate when reconciling this Kustomization. - type: string - sourceRef: - description: Reference of the source where the kustomization file is. - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - GitRepository - - Bucket - type: string - name: - description: Name of the referent - type: string - namespace: - description: Namespace of the referent, defaults to the Kustomization namespace - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent kustomize executions, it does not apply to already started executions. Defaults to false. - type: boolean - targetNamespace: - description: TargetNamespace sets or overrides the namespace in the kustomization.yaml file. - maxLength: 63 - minLength: 1 - type: string - timeout: - description: Timeout for validation, apply and health checking operations. Defaults to 'Interval' duration. - type: string - validation: - description: Validate the Kubernetes objects before applying them on the cluster. The validation strategy can be 'client' (local dry-run), 'server' (APIServer dry-run) or 'none'. When 'Force' is 'true', validation will fallback to 'client' if set to 'server' because server-side validation is not supported in this scenario. - enum: - - none - - client - - server - type: string - required: - - interval - - prune - - sourceRef - type: object - status: - description: KustomizationStatus defines the observed state of a kustomization. - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastAppliedRevision: - description: The last successfully applied revision. The revision format for Git sources is /. - type: string - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation attempt. - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - snapshot: - description: The last successfully applied revision metadata. - properties: - checksum: - description: The manifests sha1 checksum. - type: string - entries: - description: A list of Kubernetes kinds grouped by namespace. - items: - description: Snapshot holds the metadata of namespaced Kubernetes objects - properties: - kinds: - additionalProperties: - type: string - description: The list of Kubernetes kinds. - type: object - namespace: - description: The namespace of this entry. - type: string - required: - - kinds - type: object - type: array - required: - - checksum - - entries - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: providers.notification.toolkit.fluxcd.io -spec: - group: notification.toolkit.fluxcd.io - names: - kind: Provider - listKind: ProviderList - plural: providers - singular: provider - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Provider is the Schema for the providers API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ProviderSpec defines the desired state of Provider - properties: - address: - description: HTTP/S webhook address of this provider - pattern: ^(http|https):// - type: string - certSecretRef: - description: CertSecretRef can be given the name of a secret containing a PEM-encoded CA certificate (`caFile`) - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - channel: - description: Alert channel for this provider - type: string - proxy: - description: HTTP/S address of the proxy - pattern: ^(http|https):// - type: string - secretRef: - description: Secret reference containing the provider webhook URL using "address" as data key - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - type: - description: Type of provider - enum: - - slack - - discord - - msteams - - rocket - - generic - - github - - gitlab - - bitbucket - - azuredevops - - googlechat - - webex - - sentry - type: string - username: - description: Bot username for this provider - type: string - required: - - type - type: object - status: - description: ProviderStatus defines the observed state of Provider - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: receivers.notification.toolkit.fluxcd.io -spec: - group: notification.toolkit.fluxcd.io - names: - kind: Receiver - listKind: ReceiverList - plural: receivers - singular: receiver - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Receiver is the Schema for the receivers API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ReceiverSpec defines the desired state of Receiver - properties: - events: - description: A list of events to handle, e.g. 'push' for GitHub or 'Push Hook' for GitLab. - items: - type: string - type: array - resources: - description: A list of resources to be notified about changes. - items: - description: CrossNamespaceObjectReference contains enough information to let you locate the typed referenced object at cluster level - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - Bucket - - GitRepository - - Kustomization - - HelmRelease - - HelmChart - - HelmRepository - - ImageRepository - - ImagePolicy - - ImageUpdateAutomation - type: string - name: - description: Name of the referent - maxLength: 53 - minLength: 1 - type: string - namespace: - description: Namespace of the referent - maxLength: 53 - minLength: 1 - type: string - required: - - name - type: object - type: array - secretRef: - description: Secret reference containing the token used to validate the payload authenticity - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent events handling. Defaults to false. - type: boolean - type: - description: Type of webhook sender, used to determine the validation procedure and payload deserialization. - enum: - - generic - - generic-hmac - - github - - gitlab - - bitbucket - - harbor - - dockerhub - - quay - - gcr - - nexus - - acr - type: string - required: - - resources - - type - type: object - status: - description: ReceiverStatus defines the observed state of Receiver - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: Generated webhook URL in the format of '/hook/sha256sum(token+name+namespace)'. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: image-automation-controller - namespace: flux-system ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: image-reflector-controller - namespace: flux-system ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: kustomize-controller - namespace: flux-system ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: notification-controller - namespace: flux-system ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: source-controller - namespace: flux-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: crd-controller-flux-system -rules: -- apiGroups: - - source.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - kustomize.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - helm.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - notification.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - image.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch -- apiGroups: - - "" - resources: - - configmaps - - configmaps/status - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: cluster-reconciler-flux-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: -- kind: ServiceAccount - name: kustomize-controller - namespace: flux-system -- kind: ServiceAccount - name: helm-controller - namespace: flux-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: crd-controller-flux-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: crd-controller-flux-system -subjects: -- kind: ServiceAccount - name: kustomize-controller - namespace: flux-system -- kind: ServiceAccount - name: helm-controller - namespace: flux-system -- kind: ServiceAccount - name: source-controller - namespace: flux-system -- kind: ServiceAccount - name: notification-controller - namespace: flux-system -- kind: ServiceAccount - name: image-reflector-controller - namespace: flux-system -- kind: ServiceAccount - name: image-automation-controller - namespace: flux-system ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: notification-controller - namespace: flux-system -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: http - selector: - app: notification-controller - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: source-controller - namespace: flux-system -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: http - selector: - app: source-controller - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: webhook-receiver - namespace: flux-system -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: http-webhook - selector: - app: notification-controller - type: ClusterIP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: image-automation-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: image-automation-controller - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: image-automation-controller - spec: - containers: - - args: - - --events-addr=http://notification-controller/ - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/image-automation-controller:v0.9.1 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9440 - name: healthz - protocol: TCP - - containerPort: 8080 - name: http-prom - readinessProbe: - httpGet: - path: /readyz - port: healthz - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /tmp - name: temp - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 1337 - serviceAccountName: image-automation-controller - terminationGracePeriodSeconds: 10 - volumes: - - emptyDir: {} - name: temp ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: image-reflector-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: image-reflector-controller - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: image-reflector-controller - spec: - containers: - - args: - - --events-addr=http://notification-controller/ - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/image-reflector-controller:v0.9.1 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9440 - name: healthz - protocol: TCP - - containerPort: 8080 - name: http-prom - readinessProbe: - httpGet: - path: /readyz - port: healthz - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /tmp - name: temp - - mountPath: /data - name: data - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 1337 - serviceAccountName: image-reflector-controller - terminationGracePeriodSeconds: 10 - volumes: - - emptyDir: {} - name: temp - - emptyDir: {} - name: data ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: kustomize-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: kustomize-controller - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: kustomize-controller - spec: - containers: - - args: - - --events-addr=http://notification-controller/ - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/kustomize-controller:v0.12.0 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9440 - name: healthz - protocol: TCP - - containerPort: 8080 - name: http-prom - readinessProbe: - httpGet: - path: /readyz - port: healthz - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /tmp - name: temp - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 1337 - serviceAccountName: kustomize-controller - terminationGracePeriodSeconds: 60 - volumes: - - emptyDir: {} - name: temp ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: notification-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: notification-controller - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: notification-controller - spec: - containers: - - args: - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/notification-controller:v0.13.0 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9440 - name: healthz - protocol: TCP - - containerPort: 9090 - name: http - - containerPort: 9292 - name: http-webhook - - containerPort: 8080 - name: http-prom - readinessProbe: - httpGet: - path: /readyz - port: healthz - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /tmp - name: temp - nodeSelector: - kubernetes.io/os: linux - serviceAccountName: notification-controller - terminationGracePeriodSeconds: 10 - volumes: - - emptyDir: {} - name: temp ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - control-plane: controller - name: source-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: source-controller - strategy: - type: Recreate - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: source-controller - spec: - containers: - - args: - - --events-addr=http://notification-controller/ - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - - --storage-path=/data - - --storage-adv-addr=source-controller.$(RUNTIME_NAMESPACE).svc.cluster.local. - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/source-controller:v0.12.2 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9090 - name: http - - containerPort: 8080 - name: http-prom - - containerPort: 9440 - name: healthz - readinessProbe: - httpGet: - path: / - port: http - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 50m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /data - name: data - - mountPath: /tmp - name: tmp - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 1337 - serviceAccountName: source-controller - terminationGracePeriodSeconds: 10 - volumes: - - emptyDir: {} - name: data - - emptyDir: {} - name: tmp ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: allow-egress - namespace: flux-system -spec: - egress: - - {} - ingress: - - from: - - podSelector: {} - podSelector: {} - policyTypes: - - Ingress - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: allow-scraping - namespace: flux-system -spec: - ingress: - - from: - - namespaceSelector: {} - ports: - - port: 8080 - protocol: TCP - podSelector: {} - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.13.4 - name: allow-webhooks - namespace: flux-system -spec: - ingress: - - from: - - namespaceSelector: {} - podSelector: - matchLabels: - app: notification-controller - policyTypes: - - Ingress ---- diff --git a/deploy/bases/frontend-registry.yaml b/deploy/bases/frontend-registry.yaml deleted file mode 100644 index f9c5a8dee8..0000000000 --- a/deploy/bases/frontend-registry.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: frontend - namespace: flux-system -spec: - image: gcr.io/track-compliance/frontend - interval: 1m0s - diff --git a/deploy/bases/guidance-registry.yaml b/deploy/bases/guidance-registry.yaml deleted file mode 100644 index be51327797..0000000000 --- a/deploy/bases/guidance-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: guidance - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/guidance - interval: 5m0s diff --git a/deploy/bases/https-scanner-registry.yaml b/deploy/bases/https-scanner-registry.yaml deleted file mode 100644 index a2635ea87c..0000000000 --- a/deploy/bases/https-scanner-registry.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: https-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/scanners/https - interval: 1m0s - diff --git a/deploy/bases/kustomization.yaml b/deploy/bases/kustomization.yaml deleted file mode 100644 index 30e4fa7dff..0000000000 --- a/deploy/bases/kustomization.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- flux.yaml -- api-registry.yaml -- frontend-registry.yaml -- autoscan-registry.yaml -- dmarc-report-registry.yaml -- dns-scanner-registry.yaml -- https-scanner-registry.yaml -- result-processor-registry.yaml -- ssl-scanner-registry.yaml -- tracker-repo.yaml -- app-kustomization.yaml -- platform-kustomization.yaml -- summaries-registry.yaml -- guidance-registry.yaml -- super-admin-registry.yaml -- queue-registries.yaml diff --git a/deploy/bases/platform-kustomization.yaml b/deploy/bases/platform-kustomization.yaml deleted file mode 100644 index 6265f05a12..0000000000 --- a/deploy/bases/platform-kustomization.yaml +++ /dev/null @@ -1,20 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: platform - namespace: flux-system -spec: - interval: 2m0s - path: ./platform/gke - prune: true - sourceRef: - kind: GitRepository - name: tracker - healthChecks: - - kind: Deployment - name: istio-ingressgateway - namespace: istio-system - - kind: Deployment - name: istiod - namespace: istio-system diff --git a/deploy/bases/queue-registries.yaml b/deploy/bases/queue-registries.yaml deleted file mode 100644 index 98c47839e9..0000000000 --- a/deploy/bases/queue-registries.yaml +++ /dev/null @@ -1,41 +0,0 @@ - ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: scan-queue - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/queues/scan - interval: 1m0s - ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: result-queue - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/queues/result - interval: 1m0s - ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: ots-scan-queue - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/queues/ots-scan - interval: 1m0s - - ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: ots-result-queue - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/queues/ots-result - interval: 1m0s diff --git a/deploy/bases/result-processor-registry.yaml b/deploy/bases/result-processor-registry.yaml deleted file mode 100644 index 6b1a8945ee..0000000000 --- a/deploy/bases/result-processor-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: result-processor - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/results - interval: 1m0s diff --git a/deploy/bases/ssl-scanner-registry.yaml b/deploy/bases/ssl-scanner-registry.yaml deleted file mode 100644 index 424a4c17c0..0000000000 --- a/deploy/bases/ssl-scanner-registry.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: ssl-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/scanners/ssl - interval: 1m0s - diff --git a/deploy/bases/summaries-registry.yaml b/deploy/bases/summaries-registry.yaml deleted file mode 100644 index 9661f82bc3..0000000000 --- a/deploy/bases/summaries-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: summaries - namespace: flux-system -spec: - image: gcr.io/track-compliance/services/summaries - interval: 5m0s diff --git a/deploy/bases/super-admin-registry.yaml b/deploy/bases/super-admin-registry.yaml deleted file mode 100644 index 58be0e1f2d..0000000000 --- a/deploy/bases/super-admin-registry.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: super-admin - namespace: flux-system -spec: - image: gcr.io/track-compliance/super-admin - interval: 5m0s diff --git a/deploy/bases/tracker-repo.yaml b/deploy/bases/tracker-repo.yaml deleted file mode 100644 index f721a91d72..0000000000 --- a/deploy/bases/tracker-repo.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1beta1 -kind: GitRepository -metadata: - name: tracker - namespace: flux-system -spec: - interval: 1m0s - ref: - branch: master - url: https://github.com/canada-ca/tracker - diff --git a/deploy/creds/bases/flux-system-namespace.yaml b/deploy/creds/bases/flux-system-namespace.yaml deleted file mode 100644 index 8121c86a90..0000000000 --- a/deploy/creds/bases/flux-system-namespace.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/version: v0.8.2 - name: flux-system - diff --git a/deploy/creds/bases/kustomization.yaml b/deploy/creds/bases/kustomization.yaml deleted file mode 100644 index 5c2e97837a..0000000000 --- a/deploy/creds/bases/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- flux-system-namespace.yaml diff --git a/deploy/creds/kustomization.yaml b/deploy/creds/kustomization.yaml deleted file mode 100644 index d471165183..0000000000 --- a/deploy/creds/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Kustomization -resources: - - flux-system-namespace.yaml diff --git a/deploy/creds/readwrite/kustomization.yaml b/deploy/creds/readwrite/kustomization.yaml deleted file mode 100644 index 13007d07b4..0000000000 --- a/deploy/creds/readwrite/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component -resources: - - ../bases -secretGenerator: -- files: - - identity - - identity.pub - - known_hosts - name: flux-credentials - namespace: flux-system -generatorOptions: - disableNameSuffixHash: true diff --git a/deploy/gke/api-image-policy.yaml b/deploy/gke/api-image-policy.yaml deleted file mode 100644 index 0e812712ca..0000000000 --- a/deploy/gke/api-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: api - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: api - policy: - numerical: - order: asc - diff --git a/deploy/gke/autoscan-image-policy.yaml b/deploy/gke/autoscan-image-policy.yaml deleted file mode 100644 index 66f0ace515..0000000000 --- a/deploy/gke/autoscan-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: autoscan - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: autoscan - policy: - numerical: - order: asc - diff --git a/deploy/gke/dmarc-report-image-policy.yaml b/deploy/gke/dmarc-report-image-policy.yaml deleted file mode 100644 index 6afb2ca902..0000000000 --- a/deploy/gke/dmarc-report-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: dmarc-report - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: dmarc-report - policy: - numerical: - order: asc - diff --git a/deploy/gke/dns-scanner-image-policy.yaml b/deploy/gke/dns-scanner-image-policy.yaml deleted file mode 100644 index 835635a8fc..0000000000 --- a/deploy/gke/dns-scanner-image-policy.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: dns-scanner - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: dns-scanner - policy: - numerical: - order: asc diff --git a/deploy/gke/frontend-image-policy.yaml b/deploy/gke/frontend-image-policy.yaml deleted file mode 100644 index 3cbf8211e6..0000000000 --- a/deploy/gke/frontend-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: frontend - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: frontend - policy: - numerical: - order: asc - diff --git a/deploy/gke/guidance-image-policy.yaml b/deploy/gke/guidance-image-policy.yaml deleted file mode 100644 index 7a65922d89..0000000000 --- a/deploy/gke/guidance-image-policy.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: guidance - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: guidance - policy: - numerical: - order: asc diff --git a/deploy/gke/https-scanner-image-policy.yaml b/deploy/gke/https-scanner-image-policy.yaml deleted file mode 100644 index 36e8d2c461..0000000000 --- a/deploy/gke/https-scanner-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: https-scanner - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: https-scanner - policy: - numerical: - order: asc - diff --git a/deploy/gke/kustomization.yaml b/deploy/gke/kustomization.yaml deleted file mode 100644 index 07b4524f7a..0000000000 --- a/deploy/gke/kustomization.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- api-image-policy.yaml -- frontend-image-policy.yaml -- autoscan-image-policy.yaml -- dmarc-report-image-policy.yaml -- dns-scanner-image-policy.yaml -- https-scanner-image-policy.yaml -- result-processor-image-policy.yaml -- ssl-scanner-image-policy.yaml -- update-automation.yaml -- summaries-image-policy.yaml -- guidance-image-policy.yaml -- super-admin-image-policy.yaml -- queue-image-policies.yaml -patchesStrategicMerge: -- tracker-repo.yaml diff --git a/deploy/gke/queue-image-policies.yaml b/deploy/gke/queue-image-policies.yaml deleted file mode 100644 index 7bb73e5a60..0000000000 --- a/deploy/gke/queue-image-policies.yaml +++ /dev/null @@ -1,60 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: scan-queue - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: scan-queue - policy: - numerical: - order: asc ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: result-queue - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: result-queue - policy: - numerical: - order: asc ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: ots-scan-queue - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: ots-scan-queue - policy: - numerical: - order: asc ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: ots-result-queue - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: ots-result-queue - policy: - numerical: - order: asc diff --git a/deploy/gke/result-processor-image-policy.yaml b/deploy/gke/result-processor-image-policy.yaml deleted file mode 100644 index 7097e1228a..0000000000 --- a/deploy/gke/result-processor-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: result-processor - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: result-processor - policy: - numerical: - order: asc - diff --git a/deploy/gke/ssl-scanner-image-policy.yaml b/deploy/gke/ssl-scanner-image-policy.yaml deleted file mode 100644 index 4163e72242..0000000000 --- a/deploy/gke/ssl-scanner-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: ssl-scanner - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: ssl-scanner - policy: - numerical: - order: asc - diff --git a/deploy/gke/summaries-image-policy.yaml b/deploy/gke/summaries-image-policy.yaml deleted file mode 100644 index eba1d6ea9c..0000000000 --- a/deploy/gke/summaries-image-policy.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: summaries - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: summaries - policy: - numerical: - order: asc diff --git a/deploy/gke/super-admin-image-policy.yaml b/deploy/gke/super-admin-image-policy.yaml deleted file mode 100644 index a275a12c8d..0000000000 --- a/deploy/gke/super-admin-image-policy.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: super-admin - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: super-admin - policy: - numerical: - order: asc diff --git a/deploy/gke/tracker-repo.yaml b/deploy/gke/tracker-repo.yaml deleted file mode 100644 index a04bb17009..0000000000 --- a/deploy/gke/tracker-repo.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1beta1 -kind: GitRepository -metadata: - name: tracker - namespace: flux-system -spec: - interval: 1m0s - ref: - branch: master - secretRef: - name: flux-credentials - url: ssh://git@github.com/canada-ca/tracker - diff --git a/deploy/gke/update-automation.yaml b/deploy/gke/update-automation.yaml deleted file mode 100644 index ea0940ee69..0000000000 --- a/deploy/gke/update-automation.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageUpdateAutomation -metadata: - name: tracker-updater - namespace: flux-system -spec: - sourceRef: - kind: GitRepository - name: tracker - interval: 5m - update: - strategy: Setters - path: . - git: - checkout: - ref: - branch: master - commit: - author: - name: fluxbot - email: fluxcd@users.noreply.github.com - messageTemplate: '[ci skip] {{range .Updated.Images}}{{println .}}{{end}}' - push: - branch: master diff --git a/deploy/minikube/app-kustomization.yaml b/deploy/minikube/app-kustomization.yaml deleted file mode 100644 index 3fe67d4d44..0000000000 --- a/deploy/minikube/app-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: app - namespace: flux-system -spec: - path: ./app/minikube diff --git a/deploy/minikube/kustomization.yaml b/deploy/minikube/kustomization.yaml deleted file mode 100644 index 3669131f60..0000000000 --- a/deploy/minikube/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -patchesStrategicMerge: -- app-kustomization.yaml -- platform-kustomization.yaml diff --git a/deploy/minikube/platform-kustomization.yaml b/deploy/minikube/platform-kustomization.yaml deleted file mode 100644 index 8b53ab7e41..0000000000 --- a/deploy/minikube/platform-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: platform - namespace: flux-system -spec: - path: ./platform/minikube diff --git a/deploy/production/app-kustomization.yaml b/deploy/production/app-kustomization.yaml deleted file mode 100644 index cbb81732aa..0000000000 --- a/deploy/production/app-kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: app - namespace: flux-system -spec: - path: ./app/production - diff --git a/deploy/production/kustomization.yaml b/deploy/production/kustomization.yaml deleted file mode 100644 index 3669131f60..0000000000 --- a/deploy/production/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -patchesStrategicMerge: -- app-kustomization.yaml -- platform-kustomization.yaml diff --git a/deploy/production/platform-kustomization.yaml b/deploy/production/platform-kustomization.yaml deleted file mode 100644 index bd1d18021b..0000000000 --- a/deploy/production/platform-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: platform - namespace: flux-system -spec: - path: ./platform/production diff --git a/deploy/staging/app-kustomization.yaml b/deploy/staging/app-kustomization.yaml deleted file mode 100644 index 8e27b08d92..0000000000 --- a/deploy/staging/app-kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: app - namespace: flux-system -spec: - path: ./app/staging - diff --git a/deploy/staging/kustomization.yaml b/deploy/staging/kustomization.yaml deleted file mode 100644 index 3669131f60..0000000000 --- a/deploy/staging/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -patchesStrategicMerge: -- app-kustomization.yaml -- platform-kustomization.yaml diff --git a/deploy/staging/platform-kustomization.yaml b/deploy/staging/platform-kustomization.yaml deleted file mode 100644 index 07afe60ba5..0000000000 --- a/deploy/staging/platform-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: platform - namespace: flux-system -spec: - path: ./platform/staging diff --git a/deploy/test/app-kustomization.yaml b/deploy/test/app-kustomization.yaml deleted file mode 100644 index d0f295df61..0000000000 --- a/deploy/test/app-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: app - namespace: flux-system -spec: - path: ./app/test diff --git a/deploy/test/kustomization.yaml b/deploy/test/kustomization.yaml deleted file mode 100644 index 3669131f60..0000000000 --- a/deploy/test/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -patchesStrategicMerge: -- app-kustomization.yaml -- platform-kustomization.yaml diff --git a/deploy/test/platform-kustomization.yaml b/deploy/test/platform-kustomization.yaml deleted file mode 100644 index 644213f4dc..0000000000 --- a/deploy/test/platform-kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: platform - namespace: flux-system -spec: - path: ./platform/test diff --git a/frontend-maintenance/.dockerignore b/frontend-maintenance/.dockerignore new file mode 100644 index 0000000000..c8264d1c6e --- /dev/null +++ b/frontend-maintenance/.dockerignore @@ -0,0 +1,5 @@ +cloudbuild.yaml +Dockerfile +LICENCE +README.md +.dockerignore diff --git a/frontend-maintenance/Dockerfile b/frontend-maintenance/Dockerfile new file mode 100644 index 0000000000..898b8b01e6 --- /dev/null +++ b/frontend-maintenance/Dockerfile @@ -0,0 +1,12 @@ +FROM nginxinc/nginx-unprivileged:1.25-alpine3.17 + +RUN rm -rf /etc/nginx/html/* + +COPY ./nginx.conf /etc/nginx/nginx.conf +COPY ./html /usr/share/nginx/html + +WORKDIR /app + +EXPOSE 8080 + +CMD ["nginx-debug", "-g", "daemon off;"] diff --git a/frontend-maintenance/LICENCE b/frontend-maintenance/LICENCE new file mode 100644 index 0000000000..b934c80f2d --- /dev/null +++ b/frontend-maintenance/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Government of Canada - Gouvernement du Canada + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/frontend-maintenance/cloudbuild.yaml b/frontend-maintenance/cloudbuild.yaml new file mode 100644 index 0000000000..df4af9bc4d --- /dev/null +++ b/frontend-maintenance/cloudbuild.yaml @@ -0,0 +1,37 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + id: generate-image-name + entrypoint: "bash" + dir: frontend-maintenance + args: + - "-c" + - | + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/frontend-maintenance:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: "gcr.io/cloud-builders/docker" + id: build + entrypoint: "bash" + dir: frontend-maintenance + args: + - "-c" + - | + image=$(cat /workspace/imagename) + docker build -t $image . + + - name: "gcr.io/cloud-builders/docker" + id: push-if-master + entrypoint: "bash" + args: + - "-c" + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi + +timeout: 1200s +options: + machineType: "E2_HIGHCPU_8" diff --git a/frontend-maintenance/html/canada-wordmark.svg b/frontend-maintenance/html/canada-wordmark.svg new file mode 100644 index 0000000000..e03c1d46b4 --- /dev/null +++ b/frontend-maintenance/html/canada-wordmark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/frontend-maintenance/html/goc-header-logo-dark-en.svg b/frontend-maintenance/html/goc-header-logo-dark-en.svg new file mode 100644 index 0000000000..6a5ee2254e --- /dev/null +++ b/frontend-maintenance/html/goc-header-logo-dark-en.svg @@ -0,0 +1 @@ +goc--header-logo diff --git a/frontend-maintenance/html/index.html b/frontend-maintenance/html/index.html new file mode 100644 index 0000000000..e5a74f39c8 --- /dev/null +++ b/frontend-maintenance/html/index.html @@ -0,0 +1,129 @@ + + + + + Tracker Maintenance - Maintenance Suivi + + + + + + +
+ +
+ + +
+ +
+

Tracker is under maintenance

+
+

Tracker is currently down for maintenance.

+

If you require assistance, please email zzTBSCyber@tbs-sct.gc.ca.

+
+
+ +
+ +
+

Suivi est en maintenance

+
+

Suivi est actuellement en panne pour cause de maintenance.

+

Si vous avez besoin d'aide, veuillez envoyer un courriel à zzTBSCyber@tbs-sct.gc.ca.

+
+
+ +
+ +
+ Government of Canada wordmark. Mot-symbole du gouvernement du Canada. +
+ + diff --git a/frontend-maintenance/html/tracker-hardhat.svg b/frontend-maintenance/html/tracker-hardhat.svg new file mode 100644 index 0000000000..7e54da74ed --- /dev/null +++ b/frontend-maintenance/html/tracker-hardhat.svg @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend-maintenance/nginx.conf b/frontend-maintenance/nginx.conf new file mode 100644 index 0000000000..894d996cc5 --- /dev/null +++ b/frontend-maintenance/nginx.conf @@ -0,0 +1,27 @@ +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include mime.types; + sendfile on; + + server { + listen 8080; + listen [::]:8080; + + gzip_static on; + + location ~ .*/(.*.svg)$ { + root /usr/share/nginx/html; + try_files $uri /$1; + } + + location / { + root /usr/share/nginx/html; + try_files $uri /index.html; + } + } +} diff --git a/frontend/.eslintrc b/frontend/.eslintrc index a23c71e131..eff689c3c4 100644 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -1,20 +1,23 @@ { - "extends": [ - "standard", - "prettier", - "plugin:react/recommended", - "plugin:import/errors", - "plugin:import/warnings" - ], - "plugins": ["jest", "react-hooks"], "env": { + "browser": true, + "node": true, + "es2021": true, "jest/globals": true }, + "extends": ["eslint:recommended", "plugin:react/recommended"], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["react", "jest", "import", "react-hooks"], "rules": { "react/no-unescaped-entities": ["off", {}], - "comma-dangle": ["error", "always-multiline"], "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", + "react-hooks/exhaustive-deps": "off", "no-unused-vars": [ "error", { diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 30c35410ff..96809bf101 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:alpine as build-env +FROM node:20.18.1-alpine3.19 as build-env WORKDIR /app @@ -7,11 +7,7 @@ COPY . . RUN npm ci && npm run build && npm prune --production -# https://github.com/astefanutti/scratch-node -# FROM astefanutti/scratch-node -# Our build of the above: -FROM gcr.io/track-compliance/scratch-node:16.3.0 -LABEL maintainer="mike.williamson@tbs-sct.gc.ca" +FROM node:20.18.1-alpine3.19 ENV HOST 0.0.0.0 ENV PORT 3000 @@ -21,9 +17,10 @@ WORKDIR /app COPY --from=build-env /app . ENV NODE_ENV production +# https://github.com/webpack/webpack/issues/14532#issuecomment-947012063 +ENV NODE_OPTIONS=--openssl-legacy-provider -USER node +USER nonroot EXPOSE 3000 -ENTRYPOINT ["/bin/node"] CMD ["index.js"] diff --git a/frontend/README.md b/frontend/README.md index 67610e2291..adc9c9e7c0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -57,7 +57,7 @@ This allows changes to the code to be reflected immediately in the running conta You can see the service running on `localhost:3000` and the API running on `localhost:3000/graphql`. If you want to modify the schema you can reach the editor at `localhost:3000/editor`. -#### Stopping it it +#### Stopping When you are done: diff --git a/frontend/cloudbuild.yaml b/frontend/cloudbuild.yaml index a76ff0afe4..0c68fb760f 100644 --- a/frontend/cloudbuild.yaml +++ b/frontend/cloudbuild.yaml @@ -1,79 +1,92 @@ steps: - - - name: node:alpine + - name: node:20.10.0-alpine id: install dir: frontend entrypoint: npm args: ['ci', '--no-optional'] - - name: node:alpine + - name: node:20.10.0-alpine id: lint dir: frontend entrypoint: npm args: ['run', 'lint'] - - name: node:alpine - id: test + - name: node:20.10.0-alpine + id: lingui-extract dir: frontend - entrypoint: npm - args: ['test'] + entrypoint: ash + args: + - '-c' + - | + npm run extract | tee /workspace/lingui-extract-output.txt - - name: node:alpine - id: lingui-extract + - name: node:20.10.0-alpine + id: lingui-translation-check dir: frontend - entrypoint: npm - args: ['run', 'extract'] + entrypoint: ash + args: + - '-c' + - | + output=$( awk -F '│' '/fr/ { gsub(/^[ \t]+|[ \t]+$/, "", $4); print $4 }' /workspace/lingui-extract-output.txt ) - - name: node:alpine + # Check if there are any missed french translations + if [ "$output" -eq 0 ]; then + echo "All translations are up to date, proceeding." + else + echo "There are $output missed translations, please update them before proceeding." + exit 1 + fi + + - name: node:20.10.0-alpine id: lingui-compile dir: frontend entrypoint: npm args: ['run', 'compile'] - - name: node:alpine + - name: node:20.10.0-alpine id: build-production-bundle dir: frontend entrypoint: npm - args: ['run', 'build'] + args: [ 'run', 'build' ] + + - name: node:20.10.0-alpine + id: test + dir: frontend + entrypoint: npm + args: ['test'] - name: 'gcr.io/cloud-builders/docker' id: generate-image-name entrypoint: 'bash' - dir: api-js + dir: frontend args: - '-c' - | - echo "gcr.io/$PROJECT_ID/frontend:$BRANCH_NAME-$SHORT_SHA-$(date +%s)" > /workspace/imagename - + echo "northamerica-northeast1-docker.pkg.dev/track-compliance/tracker/frontend:$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9]/-/g')-$SHORT_SHA-$(date +%s)" > /workspace/imagename - name: 'gcr.io/cloud-builders/docker' - id: build-if-master + id: build entrypoint: 'bash' dir: frontend args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then + - '-c' + - | image=$(cat /workspace/imagename) docker build -t $image . - else - exit 0 - fi - name: 'gcr.io/cloud-builders/docker' id: push-if-master entrypoint: 'bash' args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then - image=$(cat /workspace/imagename) - docker push $image - else - exit 0 - fi + - '-c' + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi timeout: 1200s options: diff --git a/frontend/docker-compose.yaml b/frontend/docker-compose.yaml index 834ffb3ed9..e2594e3d14 100644 --- a/frontend/docker-compose.yaml +++ b/frontend/docker-compose.yaml @@ -1,9 +1,11 @@ version: "3.7" services: envoy: # proxies localhost:3000 to containers based on rules in config - image: envoyproxy/envoy-alpine-dev + image: envoyproxy/envoy-alpine:v1.21.6 + network_mode: "host" entrypoint: envoy command: ["-c", "/etc/envoy-dev.yaml", "--service-cluster", "envoy"] + restart: always volumes: - ./envoy-dev.yaml:/etc/envoy-dev.yaml # Envoy configuration expose: @@ -12,25 +14,6 @@ services: ports: - "3000:3000" - "3001:3001" - frontend: # localhost:3000/* is sent to frontend - image: node:alpine - working_dir: /app - command: npm run dev - volumes: - - ./:/app - expose: - - "3000" - mocked_api: # available at localhost:3000/graphql - image: node:alpine - working_dir: /app - command: npm run mocker - volumes: - - ./mocking/:/app/mocking - - ./node_modules/:/app/node_modules/ - - ./package.json:/app/package.json - - ./.babelrc:/app/.babelrc - expose: - - "4000" volumes: driver: {} diff --git a/frontend/envoy-dev.yaml b/frontend/envoy-dev.yaml index 58c4fecc67..842d5ce572 100644 --- a/frontend/envoy-dev.yaml +++ b/frontend/envoy-dev.yaml @@ -22,9 +22,9 @@ static_resources: domains: ["*"] routes: - match: { prefix: "/graphql" } - route: { cluster: mocked_api } + route: { cluster: api } - match: { prefix: "/editor" } - route: { cluster: mocked_api } + route: { cluster: api } - match: { prefix: "/" } route: { cluster: frontend } clusters: @@ -39,20 +39,20 @@ static_resources: - endpoint: address: socket_address: - address: frontend - port_value: 3000 - - name: mocked_api + address: 127.0.0.1 + port_value: 3300 + - name: api connect_timeout: 0.25s type: strict_dns lb_policy: round_robin load_assignment: - cluster_name: mocked_api + cluster_name: api endpoints: - lb_endpoints: - endpoint: address: socket_address: - address: mocked_api + address: 127.0.0.1 port_value: 4000 admin: access_log_path: "/dev/null" diff --git a/frontend/mocking/faked_schema.js b/frontend/mocking/faked_schema.js index 206a0fc4fc..cd1b399b2f 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -2,3064 +2,6093 @@ import { gql } from '@apollo/client/core' export const getTypeNames = () => gql` type Query { - # Fetches an object given its ID + """ + Fetches an object given its ID + """ node( - # The ID of an object + """ + The ID of an object + """ id: ID! ): Node - # Fetches objects given their IDs + """ + Fetches objects given their IDs + """ nodes( - # The IDs of objects + """ + The IDs of objects + """ ids: [ID!]! ): [Node]! - # Query for dmarc summaries the user has access to. + """ + Select activity logs a user has access to. + """ + findAuditLogs( + """ + The organization you wish to query the logs from. + """ + orgId: ID + + """ + Ordering options for log connections. + """ + orderBy: LogOrder + + """ + String used to search for logs by initiant user or target resource. + """ + search: String + + """ + Keywords used to filter log results. + """ + filters: LogFilters + + """ + Returns the items in the list that come after the specified cursor. + """ + after: String + + """ + Returns the first n items from the list. + """ + first: Int + + """ + Returns the items in the list that come before the specified cursor. + """ + before: String + + """ + Returns the last n items from the list. + """ + last: Int + ): AuditLogConnection + + """ + Query for dmarc summaries the user has access to. + """ findMyDmarcSummaries( - # Ordering options for dmarc summaries connections + """ + Ordering options for dmarc summaries connections + """ orderBy: DmarcSummaryOrder - # The month in which the returned data is relevant to. + """ + The month in which the returned data is relevant to. + """ month: PeriodEnums! - # The year in which the returned data is relevant to. + """ + The year in which the returned data is relevant to. + """ year: Year! - # An optional string used to filter the results based on domains. + """ + An optional string used to filter the results based on domains. + """ search: String - # Returns the items in the list that come after the specified cursor. + """ + Filter the results based on the users affiliation. + """ + isAffiliated: Boolean + + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): DmarcSummaryConnection - # Retrieve a specific domain by providing a domain. + """ + Retrieve a specific domain by providing a domain. + """ findDomainByDomain( - # The domain you wish to retrieve information for. + """ + The domain you wish to retrieve information for. + """ domain: DomainScalar! ): Domain - # Select domains a user has access to. + """ + Select domains a user has access to. + """ findMyDomains( - # Ordering options for domain connections. + """ + Ordering options for domain connections. + """ orderBy: DomainOrder - # Limit domains to those that belong to an organization that has ownership. + """ + Limit domains to those that belong to an organization that has ownership. + """ ownership: Boolean - # String used to search for domains. + """ + String used to search for domains. + """ search: String - # Returns the items in the list that come after the specified cursor. + """ + Filter the results based on the users affiliation. + """ + isAffiliated: Boolean + + """ + Filters used to limit domains returned. + """ + filters: [DomainFilter] + + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): DomainConnection - # Select organizations a user has access to. + """ + Select organizations a user has access to. + """ findMyOrganizations( - # Ordering options for organization connections + """ + Ordering options for organization connections + """ orderBy: OrganizationOrder - # String argument used to search for organizations. + """ + String argument used to search for organizations. + """ search: String - # Filter orgs based off of the user being an admin of them. + """ + Filter orgs based off of the user being an admin of them. + """ isAdmin: Boolean - # Filter org list to either include or exclude the super admin org. + """ + Filter org list to either include or exclude the super admin org. + """ includeSuperAdminOrg: Boolean - # Returns the items in the list that come after the specified cursor. + """ + Filter org list to include only verified organizations. + """ + isVerified: Boolean + + """ + Filter the results based on the users affiliation. + """ + isAffiliated: Boolean + + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): OrganizationConnection - # Select all information on a selected organization that a user has access to. + """ + Select all information on a selected organization that a user has access to. + """ findOrganizationBySlug( - # The slugified organization name you want to retrieve data for. + """ + The slugified organization name you want to retrieve data for. + """ orgSlug: Slug! ): Organization - # Email summary computed values, used to build summary cards. + """ + CSV formatted output of all domains in all organizations including their email and web scan statuses. + """ + getAllOrganizationDomainStatuses( + """ + Filters used to limit domains returned. + """ + filters: [DomainFilter] + ): String + + """ + DKIM summary computed values, used to build summary cards. + """ + dkimSummary: CategorizedSummary + + """ + DMARC phase summary computed values, used to build summary cards. + """ + dmarcPhaseSummary: CategorizedSummary + + """ + DMARC summary computed values, used to build summary cards. + """ + dmarcSummary: CategorizedSummary + + """ + HTTPS summary computed values, used to build summary cards. + """ + httpsSummary: CategorizedSummary + + """ + Email summary computed values, used to build summary cards. + """ mailSummary: CategorizedSummary - # Web summary computed values, used to build summary cards. + """ + SPF summary computed values, used to build summary cards. + """ + spfSummary: CategorizedSummary + + """ + SSL summary computed values, used to build summary cards. + """ + sslSummary: CategorizedSummary + + """ + Web connections (HTTPS + HSTS) summary computed values, used to build summary cards. + """ + webConnectionsSummary: CategorizedSummary + + """ + Web summary computed values, used to build summary cards. + """ webSummary: CategorizedSummary - # Query the currently logged in user. + """ + Select domains a user has access to. + """ + findChartSummaries( + """ + The month in which the returned data is relevant to. + """ + month: PeriodEnums! + + """ + The year in which the returned data is relevant to. + """ + year: Year! + ): ChartSummaryConnection + + """ + Query the currently logged in user. + """ findMe: PersonalUser - # Query a specific user by user name. + """ + Select all information on a selected organization that a user has access to. + """ + findMyTracker: MyTrackerResult + + """ + Select users an admin has access to. + """ + findMyUsers( + """ + Ordering options for user affiliation + """ + orderBy: UserOrder + + """ + String used to search for users. + """ + search: String + + """ + Returns the items in the list that come after the specified cursor. + """ + after: String + + """ + Returns the first n items from the list. + """ + first: Int + + """ + Returns the items in the list that come before the specified cursor. + """ + before: String + + """ + Returns the last n items from the list. + """ + last: Int + ): UserConnection + + """ + Query a specific user by user name. + """ findUserByUsername( - # Email address of user you wish to gather data for. + """ + Email address of user you wish to gather data for. + """ userName: EmailAddress! ): SharedUser - # Query used to check if the user has an admin role. + """ + Checks if user must be logged in to access data. + """ + loginRequired: Boolean + + """ + Query used to check if the user has an admin role. + """ isUserAdmin( - # Optional org id to see if user is an admin for the requested org. + """ + Optional org id to see if user is an admin for the requested org. + """ orgId: ID ): Boolean - # Query used to check if the user has a super admin role. + """ + Query used to check if the user has a super admin role. + """ isUserSuperAdmin: Boolean - # Retrieve a specific verified domain by providing a domain. + """ + Retrieve a specific verified domain by providing a domain. + """ findVerifiedDomainByDomain( - # The domain you wish to retrieve information for. + """ + The domain you wish to retrieve information for. + """ domain: DomainScalar! ): VerifiedDomain - # Select verified check domains + """ + Select verified check domains + """ findVerifiedDomains( - # Ordering options for verified domain connections. + """ + Ordering options for verified domain connections. + """ orderBy: VerifiedDomainOrder - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): VerifiedDomainConnection - # Select all information on a selected verified organization. + """ + Select all information on a selected verified organization. + """ findVerifiedOrganizationBySlug( - # The slugified organization name you want to retrieve data for. + """ + The slugified organization name you want to retrieve data for. + """ orgSlug: Slug! ): VerifiedOrganization - # Select organizations a user has access to. + """ + Select organizations a user has access to. + """ findVerifiedOrganizations( - # Ordering options for verified organization connections. + """ + Ordering options for verified organization connections. + """ orderBy: VerifiedOrganizationOrder - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): VerifiedOrganizationConnection } - # An object with an ID + """ + An object with an ID + """ interface Node { - # The id of the object. + """ + The id of the object. + """ id: ID! } - # A connection to a list of items. - type DmarcSummaryConnection { - # Information to aid in pagination. + """ + A connection to a list of items. + """ + type AuditLogConnection { + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. - edges: [DmarcSummaryEdge] + """ + A list of edges. + """ + edges: [AuditLogEdge] - # The total amount of dmarc summaries the user has access to. + """ + The total amount of logs the user has access to. + """ totalCount: Int } - # Information about pagination in a connection. + """ + Information about pagination in a connection. + """ type PageInfo { - # When paginating forwards, are there more items? + """ + When paginating forwards, are there more items? + """ hasNextPage: Boolean! - # When paginating backwards, are there more items? + """ + When paginating backwards, are there more items? + """ hasPreviousPage: Boolean! - # When paginating backwards, the cursor to continue. + """ + When paginating backwards, the cursor to continue. + """ startCursor: String - # When paginating forwards, the cursor to continue. + """ + When paginating forwards, the cursor to continue. + """ endCursor: String } - # An edge in a connection. + """ + An edge in a connection. + """ + type AuditLogEdge { + """ + The item at the end of the edge + """ + node: AuditLog + + """ + A cursor for use in pagination + """ + cursor: String! + } + + """ + A record of activity that modified the state of a user, domain, or organization + """ + type AuditLog implements Node { + """ + The ID of an object + """ + id: ID! + + """ + Datetime string the activity occurred. + """ + timestamp: DateTime + + """ + Username of admin that initiated the activity. + """ + initiatedBy: InitiatedBy + + """ + Type of activity that was initiated. + """ + action: UserActionEnums + + """ + Information on targeted resource. + """ + target: TargetResource + + """ + Optional reason for action, used for domain removal. + """ + reason: DomainRemovalReasonEnum + } + + """ + A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the 'date-time' format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. + """ + scalar DateTime + + """ + Information on the user that initiated the logged action + """ + type InitiatedBy { + """ + The ID of an object + """ + id: ID! + + """ + User email address. + """ + userName: EmailAddress + + """ + User permission level. + """ + role: RoleEnums + + """ + User affiliated organization. + """ + organization: String + } + + """ + A field whose value conforms to the standard internet email address format as specified in HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address. + """ + scalar EmailAddress + + """ + An enum used to assign, and test users roles. + """ + enum RoleEnums { + """ + A user who has requested an invite to an organization. + """ + PENDING + + """ + A user who has been given access to view an organization. + """ + USER + + """ + A user who has the same access as a user write account, but can define new user read/write accounts. + """ + ADMIN + + """ + A user who has the same access as an admin, but can define new admins, and delete the organization. + """ + OWNER + + """ + A user who has the same access as an admin, but can define new admins. + """ + SUPER_ADMIN + } + + """ + Describes actions performed by users to modify resources. + """ + enum UserActionEnums { + """ + A new resource was created. + """ + CREATE + + """ + A resource was deleted. + """ + DELETE + + """ + An affiliation between resources was created. + """ + ADD + + """ + Properties of a resource or affiliation were modified. + """ + UPDATE + + """ + An affiliation between resources was deleted. + """ + REMOVE + + """ + A scan was requested on a resource. + """ + SCAN + + """ + A resource was exported. + """ + EXPORT + } + + """ + Resource that was the target of a specified action by a user. + """ + type TargetResource { + """ + Name of the targeted resource. + """ + resource: String + + """ + Organization that the resource is affiliated with. + """ + organization: TargetOrganization + + """ + Type of resource that was modified: user, domain, or organization. + """ + resourceType: ResourceTypeEnums + + """ + List of resource properties that were modified. + """ + updatedProperties: [UpdatedProperties] + } + + """ + Organization that the resource is affiliated with. + """ + type TargetOrganization { + """ + The ID of an object + """ + id: ID! + + """ + Name of the affiliated organization. + """ + name: String + } + + """ + Keywords used to describe resources that can be modified. + """ + enum ResourceTypeEnums { + """ + A user account affiliated with an organization. + """ + USER + + """ + An organization. + """ + ORGANIZATION + + """ + A domain affiliated with an organization. + """ + DOMAIN + } + + """ + Object describing how a resource property was updated. + """ + type UpdatedProperties { + """ + Name of updated resource. + """ + name: String + + """ + Old value of updated property. + """ + oldValue: String + + """ + New value of updated property. + """ + newValue: String + } + + """ + Reason why a domain was removed from an organization. + """ + enum DomainRemovalReasonEnum { + """ + Domain does not exist. + """ + NONEXISTENT + + """ + Domain was in the incorrect organization. + """ + WRONG_ORG + } + + """ + Ordering options for audit logs. + """ + input LogOrder { + """ + The field to order logs by. + """ + field: LogOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! + } + + """ + Properties by which domain connections can be ordered. + """ + enum LogOrderField { + """ + Order logs by timestamp. + """ + TIMESTAMP + + """ + Order logs by initiant's username. + """ + INITIATED_BY + + """ + Order logs by name of targeted resource. + """ + RESOURCE_NAME + } + + """ + Possible directions in which to order a list of items when provided an 'orderBy' argument. + """ + enum OrderDirection { + """ + Specifies an ascending order for a given 'orderBy' argument. + """ + ASC + + """ + Specifies a descending order for a given 'orderBy' argument. + """ + DESC + } + + """ + Filtering options for audit logs. + """ + input LogFilters { + """ + List of resource types to include when returning logs. + """ + resource: [ResourceTypeEnums] + + """ + List of user actions to include when returning logs. + """ + action: [UserActionEnums] + } + + """ + A connection to a list of items. + """ + type DmarcSummaryConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of edges. + """ + edges: [DmarcSummaryEdge] + + """ + The total amount of dmarc summaries the user has access to. + """ + totalCount: Int + } + + """ + An edge in a connection. + """ type DmarcSummaryEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: DmarcSummary - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # Object that contains information for a dmarc summary. + """ + Object that contains information for a dmarc summary. + """ type DmarcSummary implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # The domain that the data in this dmarc summary belongs to. + """ + The domain that the data in this dmarc summary belongs to. + """ domain: Domain - # Start date of data collection. + """ + Start date of data collection. + """ month: PeriodEnums - # End date of data collection. + """ + End date of data collection. + """ year: Year - # Category percentages based on the category totals. + """ + Category percentages based on the category totals. + """ categoryPercentages: CategoryPercentages - # Category totals for quick viewing. + """ + Category totals for quick viewing. + """ categoryTotals: CategoryTotals - # Various senders for each category. + """ + Various senders for each category. + """ detailTables: DetailTables } - # Domain object containing information for a given domain. + """ + Domain object containing information for a given domain. + """ type Domain implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Domain that scans will be ran on. + """ + Domain that scans will be ran on. + """ domain: DomainScalar - # The current dmarc phase the domain is compliant to. + """ + The current dmarc phase the domain is compliant to. + """ dmarcPhase: String - # Whether or not the domain has a aggregate dmarc report. + """ + Whether or not the domain has a aggregate dmarc report. + """ hasDMARCReport: Boolean - # The last time that a scan was ran on this domain. + """ + The last time that a scan was ran on this domain. + """ lastRan: String - # Domain Keys Identified Mail (DKIM) selector strings associated with domain. + """ + The status code when performing a DNS lookup for this domain. + """ + rcode: String + + """ + Domain Keys Identified Mail (DKIM) selector strings associated with domain. + """ selectors: [Selector] - # The domains scan status, based on the latest scan data. + """ + The domains scan status, based on the latest scan data. + """ status: DomainStatus - # The organization that this domain belongs to. + """ + Value that determines if a domain is excluded from any results and scans. + """ + archived: Boolean + + """ + Value that determines if a domain is possibly blocked. + """ + blocked: Boolean + + """ + Value that determines if a domain has a wildcard sibling. + """ + wildcardSibling: Boolean + + """ + Value that determines if a domain has a web scan pending. + """ + webScanPending: Boolean + + """ + The organization that this domain belongs to. + """ organizations( - # Ordering options for organization connections + """ + Ordering options for organization connections + """ orderBy: OrganizationOrder - # String argument used to search for organizations. + """ + String argument used to search for organizations. + """ search: String - # Filter orgs based off of the user being an admin of them. + """ + Filter orgs based off of the user being an admin of them. + """ isAdmin: Boolean - # Filter org list to either include or exclude the super admin org. + """ + Filter org list to either include or exclude the super admin org. + """ includeSuperAdminOrg: Boolean - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): OrganizationConnection - # DKIM, DMARC, and SPF scan results. - email: EmailScan + """ + DNS scan results. + """ + dnsScan( + """ + Start date for date filter. + """ + startDate: DateTime + + """ + End date for date filter. + """ + endDate: DateTime + + """ + Ordering options for DNS connections. + """ + orderBy: DNSOrder + + """ + Number of DNS scans to retrieve. + """ + limit: Int + + """ + Returns the items in the list that come after the specified cursor. + """ + after: String + + """ + Returns the first n items from the list. + """ + first: Int + + """ + Returns the items in the list that come before the specified cursor. + """ + before: String + + """ + Returns the last n items from the list. + """ + last: Int + ): DNSScanConnection + + """ + List of MX record diffs for a given domain. + """ + mxRecordDiff( + """ + Start date for date filter. + """ + startDate: DateTime + + """ + End date for date filter. + """ + endDate: DateTime + + """ + Ordering options for MX connections. + """ + orderBy: DNSOrder + + """ + Number of MX scans to retrieve. + """ + limit: Int + + """ + Returns the items in the list that come after the specified cursor. + """ + after: String + + """ + Returns the first n items from the list. + """ + first: Int + + """ + Returns the items in the list that come before the specified cursor. + """ + before: String + + """ + Returns the last n items from the list. + """ + last: Int + ): MXRecordDiffConnection + + """ + HTTPS, and TLS scan results. + """ + web( + """ + Start date for date filter. + """ + startDate: DateTime + + """ + End date for date filter. + """ + endDate: DateTime + + """ + Ordering options for web connections. + """ + orderBy: WebOrder + + """ + Number of web scans to retrieve. + """ + limit: Int + + """ + Exclude web scans which have pending status. + """ + excludePending: Boolean + + """ + Returns the items in the list that come after the specified cursor. + """ + after: String + + """ + Returns the first n items from the list. + """ + first: Int + + """ + Returns the items in the list that come before the specified cursor. + """ + before: String - # HTTPS, and SSL scan results. - web: WebScan + """ + Returns the last n items from the list. + """ + last: Int + ): WebConnection - # Summarized DMARC aggregate reports. + """ + Summarized DMARC aggregate reports. + """ dmarcSummaryByPeriod( - # The month in which the returned data is relevant to. + """ + The month in which the returned data is relevant to. + """ month: PeriodEnums! - # The year in which the returned data is relevant to. + """ + The year in which the returned data is relevant to. + """ year: Year! ): DmarcSummary - # Yearly summarized DMARC aggregate reports. + """ + Yearly summarized DMARC aggregate reports. + """ yearlyDmarcSummaries: [DmarcSummary] + + """ + List of labelled tags users of an organization have applied to the claimed domain. + """ + claimTags: [String] + + """ + Value that determines if a domain is excluded from an organization's results. + """ + hidden: Boolean + + """ + Value that determines if a user is affiliated with a domain, whether through organization affiliation, verified organization network affiliation, or through super admin status. + """ + userHasPermission: Boolean } - # String that conforms to a domain structure. + """ + String that conforms to a domain structure. + """ scalar DomainScalar - # A field that conforms to a string, with strings ending in ._domainkey. + """ + A field that conforms to a DKIM selector + """ scalar Selector - # This object contains how the domain is doing on the various scans we preform, based on the latest scan data. + """ + This object contains how the domain is doing on the various scans we preform, based on the latest scan data. + """ type DomainStatus { - # DKIM Status + """ + Certificates Status + """ + certificates: StatusEnum + + """ + Ciphers Status + """ + ciphers: StatusEnum + + """ + Curves Status + """ + curves: StatusEnum + + """ + DKIM Status + """ dkim: StatusEnum - # DMARC Status + """ + DMARC Status + """ dmarc: StatusEnum - # HTTPS Status + """ + HTTPS Status + """ https: StatusEnum - # SPF Status + """ + HSTS Status + """ + hsts: StatusEnum + + """ + Policy Status + """ + policy: StatusEnum + + """ + Protocols Status + """ + protocols: StatusEnum + + """ + SPF Status + """ spf: StatusEnum - # SSL Status + """ + SSL Status + """ ssl: StatusEnum } - # Enum used to inform front end if there are any issues, info, or the domain passes a given check. + """ + Enum used to inform front end if there are any issues, info, or the domain passes a given check. + """ enum StatusEnum { - # If the given check meets the passing requirements. + """ + If the given check meets the passing requirements. + """ PASS - # If the given check has flagged something that can provide information on the domain that aren't scan related. + """ + If the given check has flagged something that can provide information on the domain that aren't scan related. + """ INFO - # If the given check does not meet the passing requirements + """ + If the given check does not meet the passing requirements + """ FAIL } - # A connection to a list of items. + """ + A connection to a list of items. + """ type OrganizationConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [OrganizationEdge] - # The total amount of organizations the user has access to. + """ + The total amount of organizations the user has access to. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type OrganizationEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: Organization - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # Organization object containing information for a given Organization. + """ + Organization object containing information for a given Organization. + """ type Organization implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # The organizations acronym. + """ + The organizations acronym. + """ acronym: Acronym - # The full name of the organization. + """ + The full name of the organization. + """ name: String - # Slugified name of the organization. + """ + Slugified name of the organization. + """ slug: Slug - # The zone which the organization belongs to. + """ + The zone which the organization belongs to. + """ zone: String - # The sector which the organization belongs to. + """ + The sector which the organization belongs to. + """ sector: String - # The country in which the organization resides. + """ + The country in which the organization resides. + """ country: String - # The province in which the organization resides. + """ + The province in which the organization resides. + """ province: String - # The city in which the organization resides. + """ + The city in which the organization resides. + """ city: String - # Wether the organization is a verified organization. + """ + Whether the organization is a verified organization. + """ verified: Boolean - # Summaries based on scan types that are preformed on the given organizations domains. + """ + Whether the organization is externally managed. + """ + externallyManaged: Boolean + + """ + Summaries based on scan types that are preformed on the given organizations domains. + """ summaries: OrganizationSummary - # The number of domains associated with this organization. + """ + Historical summaries based on scan types that are preformed on the given organizations domains. + """ + historicalSummaries( + """ + The month in which the returned data is relevant to. + """ + month: PeriodEnums! + + """ + The year in which the returned data is relevant to. + """ + year: Year! + + """ + The direction in which to sort the data. + """ + sortDirection: OrderDirection! + ): OrganizationSummaryConnection + + """ + The number of domains associated with this organization. + """ domainCount: Int - # The domains which are associated with this organization. + """ + CSV formatted output of all domains in the organization including their email and web scan statuses. + """ + toCsv( + """ + Filters used to limit domains returned. + """ + filters: [DomainFilter] + ): String + + """ + The domains which are associated with this organization. + """ domains( - # Ordering options for domain connections. + """ + Ordering options for domain connections. + """ orderBy: DomainOrder - # Limit domains to those that belong to an organization that has ownership. + """ + Limit domains to those that belong to an organization that has ownership. + """ ownership: Boolean - # String used to search for domains. + """ + String used to search for domains. + """ search: String - # Returns the items in the list that come after the specified cursor. + """ + Filters used to limit domains returned. + """ + filters: [DomainFilter] + + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): DomainConnection - # Organization affiliations to various users. + """ + Organization affiliations to various users. + """ affiliations( - # Ordering options for affiliation connections. + """ + Ordering options for affiliation connections. + """ orderBy: AffiliationUserOrder - # String used to search for affiliated users. + """ + String used to search for affiliated users. + """ search: String - # Returns the items in the list that come after the specified cursor. + """ + Exclude (false) or include only (true) pending affiliations in the results. + """ + includePending: Boolean + + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): AffiliationConnection + + """ + Value that determines if a user is affiliated with an organization, whether through organization affiliation, verified affiliation, or through super admin status. + """ + userHasPermission: Boolean } - # A field whose value is an upper case letter or an under score that has a length between 1 and 50. + """ + A field whose value consists of upper case or lower case letters or underscores with a length between 1 and 50. + """ scalar Acronym - # A field whos values contain numbers, letters, dashes, and underscores. + """ + A field whose values contain numbers, letters, dashes, and underscores. + """ scalar Slug - # Summaries based on domains that the organization has claimed. + """ + Summaries based on domains that the organization has claimed. + """ type OrganizationSummary { - # Summary based on mail scan results for a given organization. + """ + Date that the summary was computed. + """ + date: Date + + """ + Summary based on DMARC scan results for a given organization. + """ + dmarc: CategorizedSummary + + """ + Summary based on HTTPS scan results for a given organization. + """ + https: CategorizedSummary + + """ + Summary based on mail scan results for a given organization. + """ mail: CategorizedSummary - # Summary based on web scan results for a given organization. + """ + Summary based on web scan results for a given organization. + """ web: CategorizedSummary + + """ + Summary based on DMARC phases for a given organization. + """ + dmarcPhase: CategorizedSummary + + """ + Summary based on SSL scan results for a given organization. + """ + ssl: CategorizedSummary + + """ + Summary based on HTTPS and HSTS scan results for a given organization. + """ + webConnections: CategorizedSummary + + """ + Summary based on SPF scan results for a given organization. + """ + spf: CategorizedSummary + + """ + Summary based on DKIM scan results for a given organization. + """ + dkim: CategorizedSummary } - # This object contains the list of different categories for pre-computed - # summary data with the computed total for how many domains in total are - # being compared. + """ + A date string, such as 2007-12-03, compliant with the 'full-date' format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. + """ + scalar Date + + """ + This object contains the list of different categories for pre-computed + summary data with the computed total for how many domains in total are + being compared. + """ type CategorizedSummary { - # List of SummaryCategory objects with data for different computed categories. + """ + List of SummaryCategory objects with data for different computed categories. + """ categories: [SummaryCategory] - # Total domains that were check under this summary. + """ + Total domains that were check under this summary. + """ total: Int } - # This object contains the information for each type of summary that has been pre-computed + """ + This object contains the information for each type of summary that has been pre-computed + """ type SummaryCategory { - # Category of computed summary which the other fields relate to. + """ + Category of computed summary which the other fields relate to. + """ name: String - # Total count of domains that fall into this category. + """ + Total count of domains that fall into this category. + """ count: Int - # Percentage compared to other categories. + """ + Percentage compared to other categories. + """ percentage: Float } - # A connection to a list of items. - type DomainConnection { - # Information to aid in pagination. + """ + A connection to a list of items. + """ + type OrganizationSummaryConnection { + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. - edges: [DomainEdge] + """ + A list of edges. + """ + edges: [OrganizationSummaryEdge] - # The total amount of domains the user has access to. + """ + The total amount of dmarc summaries the user has access to. + """ totalCount: Int } - # An edge in a connection. - type DomainEdge { - # The item at the end of the edge - node: Domain + """ + An edge in a connection. + """ + type OrganizationSummaryEdge { + """ + The item at the end of the edge + """ + node: OrganizationSummary - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # Ordering options for domain connections. - input DomainOrder { - # The field to order domains by. - field: DomainOrderField! + """ + An enum used to select information from the dmarc-report-api. + """ + enum PeriodEnums { + """ + The month of January. + """ + JANUARY - # The ordering direction. - direction: OrderDirection! + """ + The month of February. + """ + FEBRUARY + + """ + The month of March. + """ + MARCH + + """ + The month of April. + """ + APRIL + + """ + The month of May. + """ + MAY + + """ + The month of June. + """ + JUNE + + """ + The month of July. + """ + JULY + + """ + The month of August. + """ + AUGUST + + """ + The month of September. + """ + SEPTEMBER + + """ + The month of October. + """ + OCTOBER + + """ + The month of November. + """ + NOVEMBER + + """ + The month of December. + """ + DECEMBER + + """ + The last 30 days. + """ + LAST30DAYS + + """ + The last year. + """ + LASTYEAR + + """ + The year to date. + """ + YTD } - # Properties by which domain connections can be ordered. + """ + A field that conforms to a 4 digit integer. + """ + scalar Year + + """ + This object is used to provide filtering options when querying org-claimed domains. + """ + input DomainFilter { + """ + Category of filter to be applied. + """ + filterCategory: DomainOrderField + + """ + First value equals or does not equal second value. + """ + comparison: ComparisonEnums + + """ + Status type or tag label. + """ + filterValue: filterValueEnums + } + + """ + Properties by which domain connections can be ordered. + """ enum DomainOrderField { - # Order domains by domain. + """ + Order domains by certificates status. + """ + CERTIFICATES_STATUS + + """ + Order domains by ciphers status. + """ + CIPHERS_STATUS + + """ + Order domains by curves status. + """ + CURVES_STATUS + + """ + Order domains by domain. + """ DOMAIN - # Order domains by last ran. - LAST_RAN - - # Order domains by dkim status. + """ + Order domains by dkim status. + """ DKIM_STATUS - # Order domains by dmarc status. + """ + Order domains by dmarc status. + """ DMARC_STATUS - # Order domains by https status. + """ + Order domains by https status. + """ HTTPS_STATUS - # Order domains by spf status. + """ + Order domains by hsts status. + """ + HSTS_STATUS + + """ + Order domains by ITPIN policy status. + """ + POLICY_STATUS + + """ + Order domains by protocols status. + """ + PROTOCOLS_STATUS + + """ + Order domains by spf status. + """ SPF_STATUS - # Order domains by ssl status. - SSL_STATUS + """ + Order domains by tags. + """ + TAGS } - # Possible directions in which to order a list of items when provided an \`orderBy\` argument. - enum OrderDirection { - # Specifies an ascending order for a given \`orderBy\` argument. - ASC + """ + """ + enum ComparisonEnums { + """ + """ + EQUAL - # Specifies a descending order for a given \`orderBy\` argument. - DESC + """ + """ + NOT_EQUAL + } + + """ + """ + enum filterValueEnums { + """ + If the given check meets the passing requirements. + """ + PASS + + """ + If the given check has flagged something that can provide information on the domain that aren't scan related. + """ + INFO + + """ + If the given check does not meet the passing requirements + """ + FAIL + + """ + English label for tagging domains as new to the system. + """ + NEW + + """ + French label for tagging domains as new to the system. + """ + NOUVEAU + + """ + Bilingual Label for tagging domains as a production environment. + """ + PROD + + """ + English label for tagging domains as a staging environment. + """ + STAGING + + """ + French label for tagging domains as a staging environment. + """ + DEV + + """ + Bilingual label for tagging domains as a test environment. + """ + TEST + + """ + Bilingual label for tagging domains as web-hosting. + """ + WEB + + """ + English label for tagging domains that are not active. + """ + INACTIVE + + """ + French label for tagging domains that are not active. + """ + INACTIF + + """ + English label for tagging domains that are hidden. + """ + HIDDEN + + """ + English label for tagging domains that are archived. + """ + ARCHIVED + + """ + Label for tagging domains that have an rcode status of NXDOMAIN. + """ + NXDOMAIN + + """ + Label for tagging domains that are possibly blocked by a firewall. + """ + BLOCKED + + """ + Label for tagging domains that have a wildcard sibling. + """ + WILDCARD_SIBLING + + """ + Label for tagging domains that have a pending web scan. + """ + SCAN_PENDING + + """ + English label for tagging domains that are outside the scope of the project. + """ + OUTSIDE + + """ + French label for tagging domains that are outside the scope of the project. + """ + EXTERIEUR + } + + """ + A connection to a list of items. + """ + type DomainConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of edges. + """ + edges: [DomainEdge] + + """ + The total amount of domains the user has access to. + """ + totalCount: Int + } + + """ + An edge in a connection. + """ + type DomainEdge { + """ + The item at the end of the edge + """ + node: Domain + + """ + A cursor for use in pagination + """ + cursor: String! + } + + """ + Ordering options for domain connections. + """ + input DomainOrder { + """ + The field to order domains by. + """ + field: DomainOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! } - # A connection to a list of items. + """ + A connection to a list of items. + """ type AffiliationConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [AffiliationEdge] - # The total amount of affiliations the user has access to. + """ + The total amount of affiliations the user has access to. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type AffiliationEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: Affiliation - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # User Affiliations containing the permission level for the given organization, the users information, and the organizations information. + """ + User Affiliations containing the permission level for the given organization, the users information, and the organizations information. + """ type Affiliation implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # User's level of access to a given organization. + """ + User's level of access to a given organization. + """ permission: RoleEnums - # The affiliated users information. + """ + The affiliated users information. + """ user: SharedUser - # The affiliated organizations information. + """ + The affiliated organizations information. + """ organization: Organization } - # An enum used to assign, and test users roles. - enum RoleEnums { - # A user who has been given access to view an organization. - USER - - # A user who has the same access as a user write account, but can define new user read/write accounts. - ADMIN - - # A user who has the same access as an admin, but can define new admins. - SUPER_ADMIN - } - - # This object is used for showing none personal user details, - # and is used for limiting admins to the personal details of users. + """ + This object is used for showing none personal user details, + and is used for limiting admins to the personal details of users. + """ type SharedUser implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Users display name. + """ + Users display name. + """ displayName: String - # Users email address. + """ + Users email address. + """ userName: EmailAddress - } - # A field whose value conforms to the standard internet email address format as specified in RFC822: https://www.w3.org/Protocols/rfc822/. - scalar EmailAddress - - # Ordering options for affiliation connections. - input AffiliationUserOrder { - # The field to order affiliations by. - field: AffiliationUserOrderField! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which affiliation connections can be ordered. - enum AffiliationUserOrderField { - # Order affiliation edges by username. - USER_USERNAME - } + """ + Has the user email verified their account. + """ + emailValidated: Boolean - # Ordering options for organization connections - input OrganizationOrder { - # The field to order organizations by. - field: OrganizationOrderField! + """ + Does the user want to see new features in progress. + """ + insideUser: Boolean - # The ordering direction. - direction: OrderDirection! - } + """ + Users affiliations to various organizations. + """ + affiliations( + """ + Ordering options for affiliation connections. + """ + orderBy: AffiliationOrgOrder - # Properties by which organization connections can be ordered. - enum OrganizationOrderField { - # Order organizations by acronym. - ACRONYM + """ + String used to search for affiliated organizations. + """ + search: String - # Order organizations by name. - NAME + """ + Returns the items in the list that come after the specified cursor. + """ + after: String - # Order organizations by slug. - SLUG + """ + Returns the first n items from the list. + """ + first: Int - # Order organizations by zone. - ZONE + """ + Returns the items in the list that come before the specified cursor. + """ + before: String - # Order organizations by sector. - SECTOR + """ + Returns the last n items from the list. + """ + last: Int + ): AffiliationConnection + } - # Order organizations by country. - COUNTRY - - # Order organizations by province. - PROVINCE - - # Order organizations by city. - CITY - - # Order organizations by verified. - VERIFIED - - # Order organizations by summary mail pass count. - SUMMARY_MAIL_PASS - - # Order organizations by summary mail fail count. - SUMMARY_MAIL_FAIL - - # Order organizations by summary mail total count. - SUMMARY_MAIL_TOTAL - - # Order organizations by summary web pass count. - SUMMARY_WEB_PASS - - # Order organizations by summary web fail count. - SUMMARY_WEB_FAIL - - # Order organizations by summary web total count. - SUMMARY_WEB_TOTAL - - # Order organizations by domain count. - DOMAIN_COUNT - } - - # Results of DKIM, DMARC, and SPF scans on the given domain. - type EmailScan { - # The domain the scan was ran on. - domain: Domain - - # DomainKeys Identified Mail (DKIM) Signatures scan results. - dkim( - # Start date for date filter. - startDate: Date - - # End date for date filter. - endDate: Date - - # Ordering options for dkim connections. - orderBy: DKIMOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): DKIMConnection - - # Domain-based Message Authentication, Reporting, and Conformance (DMARC) scan results. - dmarc( - # Start date for date filter. - startDate: Date - - # End date for date filter. - endDate: Date - - # Ordering options for dmarc connections. - orderBy: DMARCOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): DMARCConnection - - # Sender Policy Framework (SPF) scan results. - spf( - # Start date for date filter. - startDate: Date - - # End date for date filter. - endDate: Date - - # Ordering options for spf connections. - orderBy: SPFOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): SPFConnection - } - - # A connection to a list of items. - type DKIMConnection { - # Information to aid in pagination. - pageInfo: PageInfo! - - # A list of edges. - edges: [DKIMEdge] - - # The total amount of dkim scans related to a given domain. - totalCount: Int - } - - # An edge in a connection. - type DKIMEdge { - # The item at the end of the edge - node: DKIM - - # A cursor for use in pagination - cursor: String! - } - - # DomainKeys Identified Mail (DKIM) permits a person, role, or - # organization that owns the signing domain to claim some - # responsibility for a message by associating the domain with the - # message. This can be an author's organization, an operational relay, - # or one of their agents. - type DKIM implements Node { - # The ID of an object - id: ID! - - # The domain the scan was ran on. - domain: Domain - - # The time when the scan was initiated. - timestamp: Date - - # Individual scans results for each DKIM selector. - results( - # Ordering options for DKIM result connections. - orderBy: DKIMResultOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): DKIMResultConnection - } - - # A date string, such as 2007-12-03, compliant with the \`full-date\` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. - scalar Date - - # A connection to a list of items. - type DKIMResultConnection { - # Information to aid in pagination. - pageInfo: PageInfo! - - # A list of edges. - edges: [DKIMResultEdge] - - # The total amount of dkim results related to a given domain. - totalCount: Int - } - - # An edge in a connection. - type DKIMResultEdge { - # The item at the end of the edge - node: DKIMResult - - # A cursor for use in pagination - cursor: String! - } - - # Individual scans results for the given DKIM selector. - type DKIMResult implements Node { - # The ID of an object - id: ID! - - # The DKIM scan information that this result belongs to. - dkim: DKIM - - # The selector the scan was ran on. - selector: String - - # DKIM record retrieved during the scan of the domain. - record: String - - # Size of the Public Key in bits - keyLength: String - - # Raw scan result. - rawJson: JSON - - # Guidance tags found during scan. - guidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - @deprecated( - reason: "This has been sub-divided into neutral, negative, and positive tags." - ) - - # Negative guidance tags found during scan. - negativeGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - - # Neutral guidance tags found during scan. - neutralGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - - # Positive guidance tags found during scan. - positiveGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - } - - # The \`JSON\` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). - scalar JSON - - # A connection to a list of items. - type GuidanceTagConnection { - # Information to aid in pagination. - pageInfo: PageInfo! - - # A list of edges. - edges: [GuidanceTagEdge] - - # The total amount of guidance tags for a given scan type. - totalCount: Int - } - - # An edge in a connection. - type GuidanceTagEdge { - # The item at the end of the edge - node: GuidanceTag - - # A cursor for use in pagination - cursor: String! - } - - # Details for a given guidance tag based on https://github.com/canada-ca/tracker/wiki/Guidance-Tags - type GuidanceTag implements Node { - # The ID of an object - id: ID! - - # The guidance tag ID. - tagId: String - - # The guidance tag name. - tagName: String - - # Guidance for changes to record, or to maintain current stance. - guidance: String - - # Links to implementation guidance for a given tag. - refLinks: [RefLinks] - - # Links to technical information for a given tag. - refLinksTech: [RefLinks] - } - - # Object containing the information of various links for guidance or technical documentation. - type RefLinks { - # Title of the guidance link. - description: String - - # URL for the guidance documentation. - refLink: String - } - - # Ordering options for guidance tag connections. - input GuidanceTagOrder { - # The field to order guidance tags by. - field: GuidanceTagOrderField! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which Guidance Tag connections can be ordered. - enum GuidanceTagOrderField { - # Order guidance tag edges by tag id. - TAG_ID - - # Order guidance tag edges by tag name. - TAG_NAME - - # Order guidance tag edges by tag guidance. - GUIDANCE - } - - # Ordering options for DKIM Result connections. - input DKIMResultOrder { - # The field to order DKIM Results by. - field: DKIMResultOrderField! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which DKIM Result connections can be ordered. - enum DKIMResultOrderField { - # Order DKIM Result edges by timestamp. - SELECTOR - - # Order DKIM Result edges by record. - RECORD - - # Order DKIM Result edges by key length. - KEY_LENGTH - } - - # Ordering options for DKIM connections. - input DKIMOrder { - # The field to order DKIM scans by. - field: DKIMOrderField! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which DKIM connections can be ordered. - enum DKIMOrderField { - # Order DKIM edges by timestamp. - TIMESTAMP - } - - # A connection to a list of items. - type DMARCConnection { - # Information to aid in pagination. - pageInfo: PageInfo! - - # A list of edges. - edges: [DMARCEdge] - - # The total amount of dmarc scans related to a given domain. - totalCount: Int - } - - # An edge in a connection. - type DMARCEdge { - # The item at the end of the edge - node: DMARC - - # A cursor for use in pagination - cursor: String! - } - - # Domain-based Message Authentication, Reporting, and Conformance - # (DMARC) is a scalable mechanism by which a mail-originating - # organization can express domain-level policies and preferences for - # message validation, disposition, and reporting, that a mail-receiving - # organization can use to improve mail handling. - type DMARC implements Node { - # The ID of an object - id: ID! - - # The domain the scan was ran on. - domain: Domain - - # The time when the scan was initiated. - timestamp: Date - - # DMARC record retrieved during scan. - record: String - - # The requested policy you wish mailbox providers to apply - # when your email fails DMARC authentication and alignment checks. - pPolicy: String - - # This tag is used to indicate a requested policy for all - # subdomains where mail is failing the DMARC authentication and alignment checks. - spPolicy: String - - # The percentage of messages to which the DMARC policy is to be applied. - pct: Int - - # Raw scan result. - rawJson: JSON - - # Guidance tags found during DMARC Scan. - guidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - @deprecated( - reason: "This has been sub-divided into neutral, negative, and positive tags." - ) - - # Negative guidance tags found during DMARC Scan. - negativeGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - - # Neutral guidance tags found during DMARC Scan. - neutralGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - - # Positive guidance tags found during DMARC Scan. - positiveGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - } - - # Ordering options for DMARC connections. - input DMARCOrder { - # The field to order DMARC scans by. - field: DmarcOrderField! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which dmarc connections can be ordered. - enum DmarcOrderField { - # Order dmarc summaries by timestamp. - TIMESTAMP - - # Order dmarc summaries by record. - RECORD - - # Order dmarc summaries by p policy. - P_POLICY - - # Order dmarc summaries by sp policy. - SP_POLICY - - # Order dmarc summaries by percentage. - PCT - } - - # A connection to a list of items. - type SPFConnection { - # Information to aid in pagination. - pageInfo: PageInfo! - - # A list of edges. - edges: [SPFEdge] - - # The total amount of spf scans related to a given domain. - totalCount: Int - } - - # An edge in a connection. - type SPFEdge { - # The item at the end of the edge - node: SPF - - # A cursor for use in pagination - cursor: String! - } - - # Email on the Internet can be forged in a number of ways. In - # particular, existing protocols place no restriction on what a sending - # host can use as the "MAIL FROM" of a message or the domain given on - # the SMTP HELO/EHLO commands. Version 1 of the Sender Policy Framework (SPF) - # protocol is where ADministrative Management Domains (ADMDs) can explicitly - # authorize the hosts that are allowed to use their domain names, and a - # receiving host can check such authorization. - type SPF implements Node { - # The ID of an object - id: ID! - - # The domain the scan was ran on. - domain: Domain - - # The time the scan was initiated. - timestamp: Date - - # The amount of DNS lookups. - lookups: Int - - # SPF record retrieved during the scan of the given domain. - record: String - - # Instruction of what a recipient should do if there is not a match to your SPF record. - spfDefault: String - - # Raw scan result. - rawJson: JSON - - # Guidance tags found during scan. - guidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - @deprecated( - reason: "This has been sub-divided into neutral, negative, and positive tags." - ) - - # Negative guidance tags found during scan. - negativeGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - - # Neutral guidance tags found during scan. - neutralGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - - # Positive guidance tags found during scan. - positiveGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - } - - # Ordering options for SPF connections. - input SPFOrder { - # The field to order SPF scans by. - field: SPFOrderField! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which SPF connections can be ordered. - enum SPFOrderField { - # Order SPF edges by timestamp. - TIMESTAMP - - # Order SPF edges by lookups. - LOOKUPS - - # Order SPF edges by record. - RECORD - - # Order SPF edges by spf-default. - SPF_DEFAULT - } - - # Results of HTTPS, and SSL scan on the given domain. - type WebScan { - # The domain the scan was ran on. - domain: Domain - - # Hyper Text Transfer Protocol Secure scan results. - https( - # Start date for date filter. - startDate: Date - - # End date for date filter. - endDate: Date - - # Ordering options for https connections. - orderBy: HTTPSOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): HTTPSConnection - - # Secure Socket Layer scan results. - ssl( - # Start date for date filter. - startDate: Date - - # End date for date filter. - endDate: Date - - # Ordering options for ssl connections. - orderBy: SSLOrder - - # Returns the items in the list that come after the specified cursor. - after: String - - # Returns the first n items from the list. - first: Int - - # Returns the items in the list that come before the specified cursor. - before: String - - # Returns the last n items from the list. - last: Int - ): SSLConnection - } - - # A connection to a list of items. - type HTTPSConnection { - # Information to aid in pagination. - pageInfo: PageInfo! - - # A list of edges. - edges: [HTTPSEdge] + """ + Ordering options for affiliation connections. + """ + input AffiliationOrgOrder { + """ + The field to order affiliations by. + """ + field: AffiliationOrgOrderField! - # The total amount of https scans for a given domain. - totalCount: Int + """ + The ordering direction. + """ + direction: OrderDirection! } - # An edge in a connection. - type HTTPSEdge { - # The item at the end of the edge - node: HTTPS + """ + Properties by which affiliation connections can be ordered. + """ + enum AffiliationOrgOrderField { + """ + Order affiliations by org acronym. + """ + ORG_ACRONYM - # A cursor for use in pagination - cursor: String! - } + """ + Order affiliations by org name. + """ + ORG_NAME - # Hyper Text Transfer Protocol Secure scan results. - type HTTPS implements Node { - # The ID of an object - id: ID! + """ + Order affiliations by org slug. + """ + ORG_SLUG - # The domain the scan was ran on. - domain: Domain + """ + Order affiliations by org zone. + """ + ORG_ZONE - # The time the scan was initiated. - timestamp: Date + """ + Order affiliations by org sector. + """ + ORG_SECTOR - # State of the HTTPS implementation on the server and any issues therein. - implementation: String + """ + Order affiliations by org country. + """ + ORG_COUNTRY - # Degree to which HTTPS is enforced on the server based on behaviour. - enforced: String + """ + Order affiliations by org province. + """ + ORG_PROVINCE - # Presence and completeness of HSTS implementation. - hsts: String + """ + Order affiliations by org city. + """ + ORG_CITY - # Denotes how long the domain should only be accessed using HTTPS - hstsAge: String + """ + Order affiliations by org verification. + """ + ORG_VERIFIED - # Denotes whether the domain has been submitted and included within HSTS preload list. - preloaded: String + """ + Order affiliations by org summary mail pass count. + """ + ORG_SUMMARY_MAIL_PASS - # Raw scan result. - rawJson: JSON + """ + Order affiliations by org summary mail fail count. + """ + ORG_SUMMARY_MAIL_FAIL - # Guidance tags found during scan. - guidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + Order affiliations by org summary mail total count. + """ + ORG_SUMMARY_MAIL_TOTAL - # Returns the items in the list that come after the specified cursor. - after: String + """ + Order affiliations by org summary web pass count. + """ + ORG_SUMMARY_WEB_PASS - # Returns the first n items from the list. - first: Int + """ + Order affiliations by org summary web fail count. + """ + ORG_SUMMARY_WEB_FAIL - # Returns the items in the list that come before the specified cursor. - before: String + """ + Order affiliations by org summary web total count. + """ + ORG_SUMMARY_WEB_TOTAL - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - @deprecated( - reason: "This has been sub-divided into neutral, negative, and positive tags." - ) + """ + Order affiliations by org domain count. + """ + ORG_DOMAIN_COUNT + } - # Negative guidance tags found during scan. - negativeGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + Ordering options for affiliation connections. + """ + input AffiliationUserOrder { + """ + The field to order affiliations by. + """ + field: AffiliationUserOrderField! - # Returns the items in the list that come after the specified cursor. - after: String + """ + The ordering direction. + """ + direction: OrderDirection! + } - # Returns the first n items from the list. - first: Int + """ + Properties by which affiliation connections can be ordered. + """ + enum AffiliationUserOrderField { + """ + Order affiliations by username. + """ + USERNAME - # Returns the items in the list that come before the specified cursor. - before: String + """ + Order affiliations by display name. + """ + DISPLAY_NAME - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + Order affiliations by permission. + """ + PERMISSION + } - # Neutral guidance tags found during scan. - neutralGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + Ordering options for organization connections + """ + input OrganizationOrder { + """ + The field to order organizations by. + """ + field: OrganizationOrderField! - # Returns the items in the list that come after the specified cursor. - after: String + """ + The ordering direction. + """ + direction: OrderDirection! + } - # Returns the first n items from the list. - first: Int + """ + Properties by which organization connections can be ordered. + """ + enum OrganizationOrderField { + """ + Order organizations by acronym. + """ + ACRONYM - # Returns the items in the list that come before the specified cursor. - before: String + """ + Order organizations by name. + """ + NAME - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + Order organizations by slug. + """ + SLUG - # Positive guidance tags found during scan. - positiveGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + Order organizations by zone. + """ + ZONE - # Returns the items in the list that come after the specified cursor. - after: String + """ + Order organizations by sector. + """ + SECTOR - # Returns the first n items from the list. - first: Int + """ + Order organizations by country. + """ + COUNTRY - # Returns the items in the list that come before the specified cursor. - before: String + """ + Order organizations by province. + """ + PROVINCE - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - } + """ + Order organizations by city. + """ + CITY - # Ordering options for HTTPS connections. - input HTTPSOrder { - # The field to order HTTPS edges by. - field: HTTPSOrderField! + """ + Order organizations by verified. + """ + VERIFIED - # The ordering direction. - direction: OrderDirection! - } + """ + Order organizations by summary mail pass count. + """ + SUMMARY_MAIL_PASS - # Properties by which HTTPS connections can be ordered. - enum HTTPSOrderField { - # Order HTTPS edges by timestamp. - TIMESTAMP + """ + Order organizations by summary mail fail count. + """ + SUMMARY_MAIL_FAIL - # Order HTTPS edges by implementation. - IMPLEMENTATION + """ + Order organizations by summary mail total count. + """ + SUMMARY_MAIL_TOTAL - # Order HTTPS edges by enforced. - ENFORCED + """ + Order organizations by summary web pass count. + """ + SUMMARY_WEB_PASS - # Order HTTPS edges by hsts. - HSTS + """ + Order organizations by summary web fail count. + """ + SUMMARY_WEB_FAIL - # Order HTTPS edges by hsts age. - HSTS_AGE + """ + Order organizations by summary web total count. + """ + SUMMARY_WEB_TOTAL - # Order HTTPS edges by preloaded. - PRELOADED + """ + Order organizations by domain count. + """ + DOMAIN_COUNT } - # A connection to a list of items. - type SSLConnection { - # Information to aid in pagination. + """ + A connection to a list of items. + """ + type DNSScanConnection { + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. - edges: [SSLEdge] + """ + A list of edges. + """ + edges: [DNSScanEdge] - # The total amount of https scans for a given domain. + """ + The total amount of DNS scans related to a given domain. + """ totalCount: Int } - # An edge in a connection. - type SSLEdge { - # The item at the end of the edge - node: SSL + """ + An edge in a connection. + """ + type DNSScanEdge { + """ + The item at the end of the edge + """ + node: DNSScan - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # Secure Socket Layer scan results. - type SSL implements Node { - # The ID of an object + """ + Results of DKIM, DMARC, and SPF scans on the given domain. + """ + type DNSScan implements Node { + """ + The ID of an object + """ id: ID! - # List of ciphers in use by the server deemed to be "acceptable". - acceptableCiphers: [String] + """ + The domain the scan was ran on. + """ + domain: String + + """ + The time when the scan was initiated. + """ + timestamp: DateTime + + """ + String of the base domain the scan was run on. + """ + baseDomain: String + + """ + Whether or not there are DNS records for the domain scanned. + """ + recordExists: Boolean + + """ + The chain CNAME/IP addresses for the domain. + """ + resolveChain: [[String]] + + """ + The CNAME for the domain (if it exists). + """ + cnameRecord: String + + """ + The MX records for the domain (if they exist). + """ + mxRecords: MXRecord + + """ + The NS records for the domain. + """ + nsRecords: NSRecord + + """ + The DMARC scan results for the domain. + """ + dmarc: DMARC + + """ + The SPF scan results for the domain. + """ + spf: SPF + + """ + The SKIM scan results for the domain. + """ + dkim: DKIM + } - # List of curves in use by the server deemed to be "acceptable". - acceptableCurves: [String] + type MXRecord { + """ + Hosts listed in the domain's MX record. + """ + hosts: [MXHost] + + """ + Additional warning info about the MX record. + """ + warnings: [String] + + """ + Error message if the MX record could not be retrieved. + """ + error: String + } + + """ + Hosts listed in the domain's MX record. + """ + type MXHost { + """ + The preference (or priority) of the host. + """ + preference: Int + + """ + The hostname of the given host. + """ + hostname: String + + """ + The IP addresses for the given host. + """ + addresses: [String] + } + + type NSRecord { + """ + Hostnames for the nameservers for the domain. + """ + hostnames: [String] + + """ + Additional warning info about the NS record. + """ + warnings: [String] + + """ + Error message if the NS record could not be retrieved. + """ + error: String + } + + """ + Domain-based Message Authentication, Reporting, and Conformance + (DMARC) is a scalable mechanism by which a mail-originating + organization can express domain-level policies and preferences for + message validation, disposition, and reporting, that a mail-receiving + organization can use to improve mail handling. + """ + type DMARC { + """ + The compliance status for DMARC for the scanned domain. + """ + status: String - # Denotes vulnerability to OpenSSL CCS Injection. - ccsInjectionVulnerable: Boolean + """ + DMARC record retrieved during scan. + """ + record: String - # The domain the scan was ran on. - domain: Domain + """ + The requested policy you wish mailbox providers to apply + when your email fails DMARC authentication and alignment checks. + """ + pPolicy: String - # Denotes vulnerability to "Heartbleed" exploit. - heartbleedVulnerable: Boolean + """ + This tag is used to indicate a requested policy for all + subdomains where mail is failing the DMARC authentication and alignment checks. + """ + spPolicy: String - # Raw scan result. - rawJson: JSON + """ + The percentage of messages to which the DMARC policy is to be applied. + """ + pct: Int - # List of ciphers in use by the server deemed to be "strong". - strongCiphers: [String] + """ + The current phase of the DMARC implementation. + """ + phase: String - # List of curves in use by the server deemed to be "strong". - strongCurves: [String] + """ + List of positive tags for the scanned domain from this scan. + """ + positiveTags: [GuidanceTag] - # Denotes support for elliptic curve key pairs. - supportsEcdhKeyExchange: Boolean + """ + List of neutral tags for the scanned domain from this scan. + """ + neutralTags: [GuidanceTag] - # The time when the scan was initiated. - timestamp: Date + """ + List of negative tags for the scanned domain from this scan. + """ + negativeTags: [GuidanceTag] + } - # List of ciphers in use by the server deemed to be "weak" or in other words, are not compliant with security standards. - weakCiphers: [String] + """ + Details for a given guidance tag based on https://github.com/canada-ca/tracker/wiki/Guidance-Tags + """ + type GuidanceTag implements Node { + """ + The ID of an object + """ + id: ID! - # List of curves in use by the server deemed to be "weak" or in other words, are not compliant with security standards. - weakCurves: [String] + """ + The guidance tag ID. + """ + tagId: String - # Guidance tags found during scan. - guidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + The guidance tag name. + """ + tagName: String - # Returns the items in the list that come after the specified cursor. - after: String + """ + Guidance for changes to record, or to maintain current stance. + """ + guidance: String - # Returns the first n items from the list. - first: Int + """ + Links to implementation guidance for a given tag. + """ + refLinks: [RefLinks] - # Returns the items in the list that come before the specified cursor. - before: String + """ + Links to technical information for a given tag. + """ + refLinksTech: [RefLinks] + } - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection - @deprecated( - reason: "This has been sub-divided into neutral, negative, and positive tags." - ) + """ + Object containing the information of various links for guidance or technical documentation. + """ + type RefLinks { + """ + Title of the guidance link. + """ + description: String - # Negative guidance tags found during scan. - negativeGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + URL for the guidance documentation. + """ + refLink: String + } - # Returns the items in the list that come after the specified cursor. - after: String + """ + Email on the Internet can be forged in a number of ways. In + particular, existing protocols place no restriction on what a sending + host can use as the "MAIL FROM" of a message or the domain given on + the SMTP HELO/EHLO commands. Version 1 of the Sender Policy Framework (SPF) + protocol is where Administrative Management Domains (ADMDs) can explicitly + authorize the hosts that are allowed to use their domain names, and a + receiving host can check such authorization. + """ + type SPF { + """ + The compliance status for SPF for the scanned domain. + """ + status: String - # Returns the first n items from the list. - first: Int + """ + SPF record retrieved during the scan of the given domain. + """ + record: String - # Returns the items in the list that come before the specified cursor. - before: String + """ + The amount of DNS lookups. + """ + lookups: Int - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + Instruction of what a recipient should do if there is not a match to your SPF record. + """ + spfDefault: String - # Neutral guidance tags found during scan. - neutralGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + List of positive tags for the scanned domain from this scan. + """ + positiveTags: [GuidanceTag] + + """ + List of neutral tags for the scanned domain from this scan. + """ + neutralTags: [GuidanceTag] + + """ + List of negative tags for the scanned domain from this scan. + """ + negativeTags: [GuidanceTag] + } + + """ + DomainKeys Identified Mail (DKIM) permits a person, role, or + organization that owns the signing domain to claim some + responsibility for a message by associating the domain with the + message. This can be an author's organization, an operational relay, + or one of their agents. + """ + type DKIM { + """ + The compliance status for DKIM for the scanned domain. + """ + status: String - # Returns the items in the list that come after the specified cursor. - after: String + """ + List of positive tags for the scanned domain from this scan. + """ + positiveTags: [GuidanceTag] + + """ + List of neutral tags for the scanned domain from this scan. + """ + neutralTags: [GuidanceTag] + + """ + List of negative tags for the scanned domain from this scan. + """ + negativeTags: [GuidanceTag] + + """ + Individual scans results for each DKIM selector. + """ + selectors: [DKIMSelectorResult] + } + + """ + DomainKeys Identified Mail (DKIM) permits a person, role, or + organization that owns the signing domain to claim some + responsibility for a message by associating the domain with the + message. This can be an author's organization, an operational relay, + or one of their agents. + """ + type DKIMSelectorResult { + """ + The selector which was scanned. + """ + selector: String - # Returns the first n items from the list. - first: Int + """ + The compliance status for DKIM for the scanned domain. + """ + status: String - # Returns the items in the list that come before the specified cursor. - before: String + """ + DKIM record retrieved during scan. + """ + record: String - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + Size of the Public Key in bits. + """ + keyLength: String - # Positive guidance tags found during scan. - positiveGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + Type of DKIM key used. + """ + keyType: String + + """ + The public exponent used for DKIM. + """ + publicExponent: Int + + """ + The key modulus used. + """ + keyModulus: String + + """ + List of positive tags for the scanned domain from this scan. + """ + positiveTags: [GuidanceTag] + + """ + List of neutral tags for the scanned domain from this scan. + """ + neutralTags: [GuidanceTag] + + """ + List of negative tags for the scanned domain from this scan. + """ + negativeTags: [GuidanceTag] + } + + """ + Ordering options for DNS connections. + """ + input DNSOrder { + """ + The field to order DNS scans by. + """ + field: DNSOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! + } - # Returns the items in the list that come after the specified cursor. - after: String + """ + Properties by which DNS connections can be ordered. + """ + enum DNSOrderField { + """ + Order DNS edges by timestamp. + """ + TIMESTAMP + } - # Returns the first n items from the list. - first: Int + """ + A connection to a list of items. + """ + type MXRecordDiffConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! - # Returns the items in the list that come before the specified cursor. - before: String + """ + A list of edges. + """ + edges: [MXRecordDiffEdge] - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + The total amount of DNS scans related to a given domain. + """ + totalCount: Int } - # Ordering options for SSL connections. - input SSLOrder { - # The field to order SSL edges by. - field: SSLOrderField! + """ + An edge in a connection. + """ + type MXRecordDiffEdge { + """ + The item at the end of the edge + """ + node: MXRecordDiff - # The ordering direction. - direction: OrderDirection! + """ + A cursor for use in pagination + """ + cursor: String! } - # Properties by which SSL connections can be ordered. - enum SSLOrderField { - # Order SSL edges by their acceptable ciphers. - ACCEPTABLE_CIPHERS - - # Order SSL edges by their acceptable curves. - ACCEPTABLE_CURVES - - # Order SSL edges by ccs-injection-vulnerable. - CCS_INJECTION_VULNERABLE + type MXRecordDiff { + """ + The ID of an object + """ + id: ID! - # Order SSL edges by heart-bleed-vulnerable. - HEARTBLEED_VULNERABLE + """ + The time when the scan was initiated. + """ + timestamp: DateTime - # Order SSL edges by their strong ciphers. - STRONG_CIPHERS + """ + The MX records for the domain (if they exist). + """ + mxRecords: MXRecord + } - # Order SSL edges by their strong curves. - STRONG_CURVES + """ + A connection to a list of items. + """ + type WebConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! - # Order SSL edges by supports-ecdh-key-exchange. - SUPPORTS_ECDH_KEY_EXCHANGE + """ + A list of edges. + """ + edges: [WebEdge] - # Order SSL edges by timestamp. - TIMESTAMP + """ + The total amount of web scans related to a given domain. + """ + totalCount: Int + } - # Order SSL edges by their weak ciphers. - WEAK_CIPHERS + """ + An edge in a connection. + """ + type WebEdge { + """ + The item at the end of the edge + """ + node: Web - # Order SSL edges by their weak curves. - WEAK_CURVES + """ + A cursor for use in pagination + """ + cursor: String! } - # An enum used to select information from the dmarc-report-api. - enum PeriodEnums { - # The month of January. - JANUARY - - # The month of February. - FEBRUARY + """ + Results of TLS and HTTP connection scans on the given domain. + """ + type Web implements Node { + """ + The ID of an object + """ + id: ID! - # The month of March. - MARCH + """ + The domain string the scan was ran on. + """ + domain: String - # The month of April. - APRIL + """ + The time when the scan was initiated. + """ + timestamp: DateTime - # The month of May. - MAY + """ + Results of the web scan at each IP address. + """ + results: [WebScan] + } - # The month of June. - JUNE + """ + Information for the TLS and HTTP connection scans on the given domain. + """ + type WebScan { + """ + IP address for scan target. + """ + ipAddress: String + + """ + The status of the scan for the given domain and IP address. + """ + status: String - # The month of July. - JULY + """ + Results of TLS and HTTP connection scans on the given domain. + """ + results: WebScanResult + } + + """ + Results of TLS and HTTP connection scans on the given domain. + """ + type WebScanResult { + """ + The time when the scan was initiated. + """ + timestamp: DateTime + + """ + The result for the TLS scan for the scanned server. + """ + tlsResult: TLSResult + + """ + The result for the HTTP connection scan for the scanned server. + """ + connectionResults: WebConnectionResult + } + + """ + Results of TLS scans on the given domain. + """ + type TLSResult { + """ + The IP address of the domain scanned. + """ + ipAddress: String + + """ + Information regarding the server which was scanned. + """ + serverLocation: ServerLocation + + """ + Information for the TLS certificate retrieved from the scanned server. + """ + certificateChainInfo: CertificateChainInfo + + """ + Whether or not the scanned server supports ECDH key exchange. + """ + supportsEcdhKeyExchange: Boolean - # The month of August. - AUGUST + """ + Whether or not the scanned server is vulnerable to heartbleed. + """ + heartbleedVulnerable: Boolean - # The month of September. - SEPTEMBER + """ + Whether or not the scanned server is vulnerable to heartbleed. + """ + robotVulnerable: String - # The month of October. - OCTOBER + """ + Whether or not the scanned server is vulnerable to CCS injection. + """ + ccsInjectionVulnerable: Boolean - # The month of November. - NOVEMBER + """ + An object containing the various TLS protocols and which suites are enabled for each protocol. + """ + acceptedCipherSuites: AcceptedCipherSuites + + """ + List of the scanned servers accepted elliptic curves and their strength. + """ + acceptedEllipticCurves: [EllipticCurve] + + """ + List of positive tags for the scanned server from this scan. + """ + positiveTags: [GuidanceTag] + + """ + List of neutral tags for the scanned server from this scan. + """ + neutralTags: [GuidanceTag] + + """ + List of negative tags for the scanned server from this scan. + """ + negativeTags: [GuidanceTag] + + """ + The compliance status of the certificate bundle for the scanned server from this scan. + """ + certificateStatus: String + + """ + The compliance status for TLS for the scanned server from this scan. + """ + sslStatus: String + + """ + The compliance status for TLS protocol for the scanned server from this scan. + """ + protocolStatus: String + + """ + The compliance status for cipher suites for the scanned server from this scan. + """ + cipherStatus: String + + """ + The compliance status for ECDH curves for the scanned server from this scan. + """ + curveStatus: String + } + + type ServerLocation { + """ + Hostname which was scanned. + """ + hostname: String + + """ + IP address used for scan. + """ + ipAddress: String + } + + """ + """ + type CertificateChainInfo { + """ + Validation results from each trust store. + """ + pathValidationResults: [PathValidationResults] + + """ + True if domain is not listed on the given TLS certificate. + """ + badHostname: Boolean + + """ + Whether or not the TLS certificate includes the OCSP Must-Staple extension. + """ + mustHaveStaple: Boolean + + """ + Whether or not the leaf (server) certificate is an Extended Validation (EV) certificate. + """ + leafCertificateIsEv: Boolean + + """ + Whether or not the certificate bundle includes the anchor (root) certificate. + """ + receivedChainContainsAnchorCertificate: Boolean + + """ + Whether or not the certificates in the certificate bundles are in the correct order. + """ + receivedChainHasValidOrder: Boolean + + """ + Whether or not any certificates in the certificate bundle were signed using the SHA1 algorithm. + """ + verifiedChainHasSha1Signature: Boolean + + """ + Whether or not the certificate chain includes a distrusted Symantec certificate. + """ + verifiedChainHasLegacySymantecAnchor: Boolean + + """ + The certificate chain which was used to create the TLS connection. + """ + certificateChain: [Certificate] + + """ + Whether or not the certificate chain passed validation. + """ + passedValidation: Boolean + } + + """ + Validation results from each trust store. + """ + type PathValidationResults { + """ + Error string which occurred when attempting to validate certificate if error exists, else null. + """ + opensslErrorString: String + + """ + Whether or not the certificate was successfully validated. + """ + wasValidationSuccessful: Boolean + + """ + Trust store used to validate TLS certificate. + """ + trustStore: TrustStore + } + + """ + Trust store used to validate TLS certificate. + """ + type TrustStore { + """ + Name of trust store used to validate certificate. + """ + name: String - # The month of December. - DECEMBER + """ + Version of trust store used to validate certificate. + """ + version: String + } + + """ + Certificate from the scanned server. + """ + type Certificate { + """ + The date which the certificate becomes initially becomes valid. + """ + notValidBefore: String + + """ + The date which the certificate becomes invalid. + """ + notValidAfter: String + + """ + The entity which signed the certificate. + """ + issuer: String + + """ + The entity for which the certificate was created for. + """ + subject: String + + """ + Whether or not the certificate is expired. + """ + expiredCert: Boolean + + """ + Whether or not the certificate is self-signed. + """ + selfSignedCert: Boolean + + """ + Whether or not the certificate has been revoked. + """ + certRevoked: Boolean + + """ + The status of the certificate revocation check. + """ + certRevokedStatus: String + + """ + The list of common names for the given certificate. + """ + commonNames: [String] + + """ + The serial number for the given certificate. + """ + serialNumber: String + + """ + The hashing algorithm used to validate this certificate. + """ + signatureHashAlgorithm: String + + """ + The list of all alternative (domain)names which can use this certificate. + """ + sanList: [String] + } + + """ + List of accepted cipher suites separated by TLS version. + """ + type AcceptedCipherSuites { + """ + Accepted cipher suites for SSL2. + """ + ssl2_0CipherSuites: [CipherSuite] + + """ + Accepted cipher suites for SSL3. + """ + ssl3_0CipherSuites: [CipherSuite] + + """ + Accepted cipher suites for TLS1.0. + """ + tls1_0CipherSuites: [CipherSuite] + + """ + Accepted cipher suites for TLS1.1. + """ + tls1_1CipherSuites: [CipherSuite] + + """ + Accepted cipher suites for TLS1.2. + """ + tls1_2CipherSuites: [CipherSuite] + + """ + Accepted cipher suites for TLS1.3. + """ + tls1_3CipherSuites: [CipherSuite] + } + + """ + Cipher suite information. + """ + type CipherSuite { + """ + The name of the cipher suite + """ + name: String - # The last 30 days. - LAST30DAYS + """ + The strength of the cipher suite. + """ + strength: String } - # A field that conforms to a 4 digit integer. - scalar Year - - # This object displays the percentages of the category totals. - type CategoryPercentages { - # Percentage of messages that are failing all checks. - failPercentage: Int - - # Percentage of messages that are passing all checks. - fullPassPercentage: Int + """ + Elliptic curve information. + """ + type EllipticCurve { + """ + The name of the elliptic curve. + """ + name: String - # Percentage of messages that are passing only dkim. - passDkimOnlyPercentage: Int + """ + The strength of the elliptic curve. + """ + strength: String + } + + """ + Results of HTTP connection scan on the given domain. + """ + type WebConnectionResult { + """ + The compliance status for HSTS for the scanned server from this scan. + """ + hstsStatus: String + + """ + The compliance status for HTTPS for the scanned server from this scan. + """ + httpsStatus: String + + """ + Whether or not the server is serving data over HTTP. + """ + httpLive: Boolean + + """ + Whether or not the server is serving data over HTTPS + """ + httpsLive: Boolean + + """ + Whether or not HTTP connection was immediately upgraded (redirected) to HTTPS. + """ + httpImmediatelyUpgrades: Boolean + + """ + Whether or not HTTP connection was eventually upgraded to HTTPS. + """ + httpEventuallyUpgrades: Boolean + + """ + Whether or not HTTPS connection is immediately downgraded to HTTP. + """ + httpsImmediatelyDowngrades: Boolean + + """ + Whether or not HTTPS connection is eventually downgraded to HTTP. + """ + httpsEventuallyDowngrades: Boolean + + """ + The parsed values for the HSTS header. + """ + hstsParsed: HSTSParsed + + """ + The IP address for the scanned server. + """ + ipAddress: String + + """ + The chain of connections created when visiting the domain using HTTP. + """ + httpChainResult: ConnectionChainResult + + """ + The chain of connections created when visiting the domain using HTTPS. + """ + httpsChainResult: ConnectionChainResult + + """ + List of positive tags for the scanned server from this scan. + """ + positiveTags: [GuidanceTag] + + """ + List of neutral tags for the scanned server from this scan. + """ + neutralTags: [GuidanceTag] + + """ + List of negative tags for the scanned server from this scan. + """ + negativeTags: [GuidanceTag] + } + + """ + The parsed values of the HSTS header. + """ + type HSTSParsed { + """ + How long to trust the HSTS header. + """ + maxAge: Int + + """ + Whether or not this HSTS policy should apply to subdomains. + """ + includeSubdomains: Boolean + + """ + Whether or not the HSTS header includes the 'preload' option. + """ + preload: Boolean + } + + """ + Information collected while checking HTTP connections while following redirects. + """ + type ConnectionChainResult { + """ + The connection protocol used for the initial connection to the server (HTTP or HTTPS). + """ + scheme: String + + """ + The domain the scan was run on. + """ + domain: String + + """ + The initial full connection URI. + """ + uri: String + + """ + Whether or not a redirection loop is created (causing endless redirects). + """ + hasRedirectLoop: Boolean + + """ + The connection chain created when following redirects. + """ + connections: [Connection] + } + + """ + An HTTP (or HTTPS) connection. + """ + type Connection { + """ + The URI for the given connection. + """ + uri: String + + """ + Detailed information for a given connection. + """ + connection: ConnectionInfo + + """ + Any errors which occurred when attempting to create this connection. + """ + error: String + + """ + The connection protocol used for this connection (HTTP or HTTPS). + """ + scheme: String + } + + """ + Detailed info for a given connection. + """ + type ConnectionInfo { + """ + The HTTP response status code. + """ + statusCode: Int + + """ + The redirect location from the HTTP response. + """ + redirectTo: String + + """ + The response headers from the HTTP response. The keys of the response are the header keys. + """ + headers: JSONObject + + """ + The detected category for the domain if blocked by firewall. + """ + blockedCategory: String + + """ + Whether or not the response included an HSTS header. + """ + HSTS: Boolean + } + + """ + The 'JSONObject' scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). + """ + scalar JSONObject + + """ + Ordering options for web connections. + """ + input WebOrder { + """ + The field to order web scans by. + """ + field: WebOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! + } - # Percentage of messages that are passing only spf. - passSpfOnlyPercentage: Int + """ + Properties by which web connections can be ordered. + """ + enum WebOrderField { + """ + Order web edges by timestamp. + """ + TIMESTAMP + } - # The total amount of messages sent by this domain. + """ + This object displays the percentages of the category totals. + """ + type CategoryPercentages { + """ + Percentage of messages that are failing all checks. + """ + failPercentage: Float + + """ + Percentage of messages that are passing all checks. + """ + fullPassPercentage: Float + + """ + Percentage of messages that are passing only dkim. + """ + passDkimOnlyPercentage: Float + + """ + Percentage of messages that are passing only spf. + """ + passSpfOnlyPercentage: Float + + """ + The total amount of messages sent by this domain. + """ totalMessages: Int } - # This object displays the total amount of messages that fit into each category. + """ + This object displays the total amount of messages that fit into each category. + """ type CategoryTotals { - # Amount of messages that are passing SPF, but failing DKIM. + """ + Amount of messages that are passing SPF, but failing DKIM. + """ passSpfOnly: Int - # Amount of messages that are passing DKIM, but failing SPF. + """ + Amount of messages that are passing DKIM, but failing SPF. + """ passDkimOnly: Int - # Amount of messages that are passing SPF and DKIM. + """ + Amount of messages that are passing SPF and DKIM. + """ fullPass: Int - # Amount of messages that fail both SPF and DKIM. + """ + Amount of messages that fail both SPF and DKIM. + """ fail: Int } - # Object that contains the various senders and details for each category. + """ + Object that contains the various senders and details for each category. + """ type DetailTables { - # List of senders that are failing DKIM checks. + """ + List of senders that are failing DKIM checks. + """ dkimFailure( - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): DkimFailureTableConnection - # List of senders that are failing DMARC checks. + """ + List of senders that are failing DMARC checks. + """ dmarcFailure( - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): DmarcFailureTableConnection - # List of senders that are passing all checks. + """ + List of senders that are passing all checks. + """ fullPass( - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): FullPassTableConnection - # List of senders that are failing SPF checks. + """ + List of senders that are failing SPF checks. + """ spfFailure( - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): SpfFailureTableConnection } - # A connection to a list of items. + """ + A connection to a list of items. + """ type DkimFailureTableConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [DkimFailureTableEdge] - # The total amount of dkim failure the user has access to. + """ + The total amount of dkim failure the user has access to. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type DkimFailureTableEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: DkimFailureTable - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # This table contains the data fields for senders who are in the DKIM fail category. + """ + This table contains the data fields for senders who are in the DKIM fail category. + """ type DkimFailureTable { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Is DKIM aligned. + """ + Is DKIM aligned. + """ dkimAligned: Boolean - # Domains used for DKIM validation + """ + Domains used for DKIM validation + """ dkimDomains: String - # The results of DKIM verification of the message. Can be pass, fail, neutral, temp-error, or perm-error. + """ + The results of DKIM verification of the message. Can be pass, fail, neutral, temp-error, or perm-error. + """ dkimResults: String - # Pointer to a DKIM public key record in DNS. + """ + Pointer to a DKIM public key record in DNS. + """ dkimSelectors: String - # Host from reverse DNS of source IP address. + """ + Host from reverse DNS of source IP address. + """ dnsHost: String - # Domain from SMTP banner message. + """ + Domain from SMTP banner message. + """ envelopeFrom: String - # Guidance for any issues that were found from the report. + """ + Guidance for any issues that were found from the report. + """ guidance: String @deprecated( - reason: "This has been turned into the \`guidanceTag\` field providing detailed information to act upon if a given tag is present." + reason: "This has been turned into the 'guidanceTag' field providing detailed information to act upon if a given tag is present." ) - # Guidance for any issues that were found from the report. + """ + Guidance for any issues that were found from the report. + """ guidanceTag: GuidanceTag - # The address/domain used in the "From" field. + """ + The address/domain used in the "From" field. + """ headerFrom: String - # IP address of sending server. + """ + IP address of sending server. + """ sourceIpAddress: String - # Total messages from this sender. + """ + Total messages from this sender. + """ totalMessages: Int } - # A connection to a list of items. + """ + A connection to a list of items. + """ type DmarcFailureTableConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [DmarcFailureTableEdge] - # The total amount of dmarc failures the user has access to. + """ + The total amount of dmarc failures the user has access to. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type DmarcFailureTableEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: DmarcFailureTable - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # This table contains the data fields for senders who are in the DMARC failure category. + """ + This table contains the data fields for senders who are in the DMARC failure category. + """ type DmarcFailureTable { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Domains used for DKIM validation + """ + Domains used for DKIM validation + """ dkimDomains: String - # Pointer to a DKIM public key record in DNS. + """ + Pointer to a DKIM public key record in DNS. + """ dkimSelectors: String - # The DMARC enforcement action that the receiver took, either none, quarantine, or reject. + """ + The DMARC enforcement action that the receiver took, either none, quarantine, or reject. + """ disposition: String - # Host from reverse DNS of source IP address. + """ + Host from reverse DNS of source IP address. + """ dnsHost: String - # Domain from SMTP banner message. + """ + Domain from SMTP banner message. + """ envelopeFrom: String - # The address/domain used in the "From" field. + """ + The address/domain used in the "From" field. + """ headerFrom: String - # IP address of sending server. + """ + IP address of sending server. + """ sourceIpAddress: String - # Domains used for SPF validation. + """ + Domains used for SPF validation. + """ spfDomains: String - # Total messages from this sender. + """ + Total messages from this sender. + """ totalMessages: Int } - # A connection to a list of items. + """ + A connection to a list of items. + """ type FullPassTableConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [FullPassTableEdge] - # The total amount of full passes the user has access to. + """ + The total amount of full passes the user has access to. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type FullPassTableEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: FullPassTable - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # This table contains the data fields for senders who are in the Full Pass category. + """ + This table contains the data fields for senders who are in the Full Pass category. + """ type FullPassTable { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Domains used for DKIM validation + """ + Domains used for DKIM validation + """ dkimDomains: String - # Pointer to a DKIM public key record in DNS. + """ + Pointer to a DKIM public key record in DNS. + """ dkimSelectors: String - # Host from reverse DNS of source IP address. + """ + Host from reverse DNS of source IP address. + """ dnsHost: String - # Domain from SMTP banner message. + """ + Domain from SMTP banner message. + """ envelopeFrom: String - # The address/domain used in the "From" field. + """ + The address/domain used in the "From" field. + """ headerFrom: String - # IP address of sending server. + """ + IP address of sending server. + """ sourceIpAddress: String - # Domains used for SPF validation. + """ + Domains used for SPF validation. + """ spfDomains: String - # Total messages from this sender. + """ + Total messages from this sender. + """ totalMessages: Int } - # A connection to a list of items. + """ + A connection to a list of items. + """ type SpfFailureTableConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [SpfFailureTableEdge] - # The total amount of spf failures the user has access to. + """ + The total amount of spf failures the user has access to. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type SpfFailureTableEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: SpfFailureTable - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # This table contains the data fields for senders who are in the SPF fail category. + """ + This table contains the data fields for senders who are in the SPF fail category. + """ type SpfFailureTable { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Host from reverse DNS of source IP address. + """ + Host from reverse DNS of source IP address. + """ dnsHost: String - # Domain from SMTP banner message. + """ + Domain from SMTP banner message. + """ envelopeFrom: String - # Guidance for any issues that were found from the report. + """ + Guidance for any issues that were found from the report. + """ guidance: String @deprecated( - reason: "This has been turned into the \`guidanceTag\` field providing detailed information to act upon if a given tag is present." + reason: "This has been turned into the 'guidanceTag' field providing detailed information to act upon if a given tag is present." ) - # Guidance for any issues that were found from the report. + """ + Guidance for any issues that were found from the report. + """ guidanceTag: GuidanceTag - # The address/domain used in the "From" field. + """ + The address/domain used in the "From" field. + """ headerFrom: String - # IP address of sending server. + """ + IP address of sending server. + """ sourceIpAddress: String - # Is SPF aligned. + """ + Is SPF aligned. + """ spfAligned: Boolean - # Domains used for SPF validation. + """ + Domains used for SPF validation. + """ spfDomains: String - # The results of DKIM verification of the message. Can be pass, fail, neutral, soft-fail, temp-error, or perm-error. + """ + The results of DKIM verification of the message. Can be pass, fail, neutral, soft-fail, temp-error, or perm-error. + """ spfResults: String - # Total messages from this sender. + """ + Total messages from this sender. + """ totalMessages: Int } - # Ordering options for dmarc summary connections. + """ + Ordering options for dmarc summary connections. + """ input DmarcSummaryOrder { - # The field to order dmarc summaries by. + """ + The field to order dmarc summaries by. + """ field: DmarcSummaryOrderField! - # The ordering direction. + """ + The ordering direction. + """ direction: OrderDirection! } - # Properties by which dmarc summary connections can be ordered. + """ + Properties by which dmarc summary connections can be ordered. + """ enum DmarcSummaryOrderField { - # Order dmarc summaries by fail count. + """ + Order dmarc summaries by fail count. + """ FAIL - # Order dmarc summaries by pass count. + """ + Order dmarc summaries by pass count. + """ FULL_PASS - # Order dmarc summaries by pass dkim only count. + """ + Order dmarc summaries by pass dkim only count. + """ PASS_DKIM_ONLY - # Order dmarc summaries by pass spf only count. + """ + Order dmarc summaries by pass spf only count. + """ PASS_SPF_ONLY - # Order dmarc summaries by fail percentage. + """ + Order dmarc summaries by fail percentage. + """ FAIL_PERCENTAGE - # Order dmarc summaries by pass percentage. + """ + Order dmarc summaries by pass percentage. + """ FULL_PASS_PERCENTAGE - # Order dmarc summaries by pass dkim only percentage. + """ + Order dmarc summaries by pass dkim only percentage. + """ PASS_DKIM_ONLY_PERCENTAGE - # Order dmarc summaries by spf only percentage. + """ + Order dmarc summaries by spf only percentage. + """ PASS_SPF_ONLY_PERCENTAGE - # Order dmarc summaries by total messages + """ + Order dmarc summaries by total messages + """ TOTAL_MESSAGES - # Order dmarc summaries by their respective domains. + """ + Order dmarc summaries by their respective domains. + """ DOMAIN } - # This object is used for showing personal user details, - # and is used for only showing the details of the querying user. + """ + A connection to a list of items. + """ + type ChartSummaryConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of edges. + """ + edges: [ChartSummaryEdge] + + """ + The total amount of dmarc summaries the user has access to. + """ + totalCount: Int + } + + """ + An edge in a connection. + """ + type ChartSummaryEdge { + """ + The item at the end of the edge + """ + node: ChartSummary + + """ + A cursor for use in pagination + """ + cursor: String! + } + + """ + This object contains the information for each type of summary that has been pre-computed + """ + type ChartSummary { + """ + The ID of an object + """ + id: ID! + + """ + Date that the summary was computed. + """ + date: Date + + """ + https summary data + """ + https: CategorizedSummary + + """ + dmarc summary data + """ + dmarc: CategorizedSummary + + """ + Summary based on mail scan results for all domains. + """ + mail: CategorizedSummary + + """ + Summary based on web scan results for all domains. + """ + web: CategorizedSummary + + """ + Summary based on DMARC phases for all domains. + """ + dmarcPhase: CategorizedSummary + + """ + Summary based on SSL scan results for all domains. + """ + ssl: CategorizedSummary + + """ + Summary based on HTTPS and HSTS scan results for all domains. + """ + webConnections: CategorizedSummary + + """ + Summary based on SPF scan results for all domains. + """ + spf: CategorizedSummary + + """ + Summary based on DKIM scan results for all domains. + """ + dkim: CategorizedSummary + } + + """ + This object is used for showing personal user details, + and is used for only showing the details of the querying user. + """ type PersonalUser implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Users email address. + """ + Users email address. + """ userName: EmailAddress - # Name displayed to other users. + """ + Name displayed to other users. + """ displayName: String - # The phone number the user has setup with tfa. + """ + The phone number the user has setup with tfa. + """ phoneNumber: PhoneNumber - # Users preferred language. + """ + Users preferred language. + """ preferredLang: LanguageEnums - # Has the user completed phone validation. + """ + Has the user completed phone validation. + """ phoneValidated: Boolean - # Has the user email verified their account. + """ + Has the user email verified their account. + """ emailValidated: Boolean - # The method in which TFA codes are sent. + """ + The method in which TFA codes are sent. + """ tfaSendMethod: TFASendMethodEnum - # Users affiliations to various organizations. + """ + Does the user want to see new features in progress. + """ + insideUser: Boolean + + """ + Does the user want to receive update emails. + """ + receiveUpdateEmails: Boolean + + """ + Users affiliations to various organizations. + """ affiliations( - # Ordering options for affiliation connections. + """ + Ordering options for affiliation connections. + """ orderBy: AffiliationOrgOrder - # String used to search for affiliated organizations. + """ + String used to search for affiliated organizations. + """ search: String - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): AffiliationConnection } - # A field whose value conforms to the standard E.164 format as specified in: https://en.wikipedia.org/wiki/E.164. Basically this is +17895551234. + """ + A field whose value conforms to the standard E.164 format as specified in: https://en.wikipedia.org/wiki/E.164. Basically this is +17895551234. + """ scalar PhoneNumber - # An enum used to define user's language. + """ + An enum used to define user's language. + """ enum LanguageEnums { - # Used for defining if English is the preferred language. + """ + Used for defining if English is the preferred language. + """ ENGLISH - # Used for defining if French is the preferred language. + """ + Used for defining if French is the preferred language. + """ FRENCH } enum TFASendMethodEnum { - # Used for defining that the TFA code will be sent via email. + """ + Used for defining that the TFA code will be sent via email. + """ EMAIL - # Used for defining that the TFA code will be sent via text. + """ + Used for defining that the TFA code will be sent via text. + """ PHONE - # User has not setup any TFA methods. + """ + User has not setup any TFA methods. + """ NONE } - # Ordering options for affiliation connections. - input AffiliationOrgOrder { - # The field to order affiliations by. - field: AffiliationOrgOrderField! + """ + Organization object containing information for a given Organization. + """ + type MyTrackerResult { + """ + Summaries based on scan types that are preformed on the given organizations domains. + """ + summaries: OrganizationSummary - # The ordering direction. - direction: OrderDirection! - } + """ + The number of domains associated with this organization. + """ + domainCount: Int - # Properties by which affiliation connections can be ordered. - enum AffiliationOrgOrderField { - # Order affiliations by org acronym. - ORG_ACRONYM + """ + The domains which are associated with this organization. + """ + domains( + """ + Ordering options for domain connections. + """ + orderBy: DomainOrder - # Order affiliations by org name. - ORG_NAME + """ + Limit domains to those that belong to an organization that has ownership. + """ + ownership: Boolean - # Order affiliations by org slug. - ORG_SLUG + """ + String used to search for domains. + """ + search: String - # Order affiliations by org zone. - ORG_ZONE + """ + Returns the items in the list that come after the specified cursor. + """ + after: String - # Order affiliations by org sector. - ORG_SECTOR + """ + Returns the first n items from the list. + """ + first: Int - # Order affiliations by org country. - ORG_COUNTRY + """ + Returns the items in the list that come before the specified cursor. + """ + before: String - # Order affiliations by org province. - ORG_PROVINCE + """ + Returns the last n items from the list. + """ + last: Int + ): DomainConnection + } - # Order affiliations by org city. - ORG_CITY + """ + A connection to a list of items. + """ + type UserConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! - # Order affiliations by org verification. - ORG_VERIFIED + """ + A list of edges. + """ + edges: [UserEdge] - # Order affiliations by org summary mail pass count. - ORG_SUMMARY_MAIL_PASS + """ + The total amount of users the user has access to. + """ + totalCount: Int + } - # Order affiliations by org summary mail fail count. - ORG_SUMMARY_MAIL_FAIL + """ + An edge in a connection. + """ + type UserEdge { + """ + The item at the end of the edge + """ + node: SharedUser - # Order affiliations by org summary mail total count. - ORG_SUMMARY_MAIL_TOTAL + """ + A cursor for use in pagination + """ + cursor: String! + } - # Order affiliations by org summary web pass count. - ORG_SUMMARY_WEB_PASS + """ + Ordering options for affiliation connections. + """ + input UserOrder { + """ + The field to order affiliations by. + """ + field: UserOrderField! - # Order affiliations by org summary web fail count. - ORG_SUMMARY_WEB_FAIL + """ + The ordering direction. + """ + direction: OrderDirection! + } - # Order affiliations by org summary web total count. - ORG_SUMMARY_WEB_TOTAL + """ + Properties by which affiliation connections can be ordered. + """ + enum UserOrderField { + """ + Order affiliation edges by username. + """ + USER_USERNAME - # Order affiliations by org domain count. - ORG_DOMAIN_COUNT + """ + Order affiliation edges by displayName. + """ + USER_DISPLAYNAME + + """ + Order affiliation edges by user verification status. + """ + USER_EMAIL_VALIDATED + + """ + Order affiliation edges by user insider status. + """ + USER_INSIDER + + """ + Order affiliation edges by amount of total affiliations. + """ + USER_AFFILIATIONS_COUNT } - # Domain object containing information for a given domain. + """ + Domain object containing information for a given domain. + """ type VerifiedDomain implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # Domain that scans will be ran on. + """ + Domain that scans will be ran on. + """ domain: DomainScalar - # The last time that a scan was ran on this domain. - lastRan: Date + """ + The last time that a scan was ran on this domain. + """ + lastRan: DateTime - # The domains scan status, based on the latest scan data. + """ + The domains scan status, based on the latest scan data. + """ status: DomainStatus - # The organization that this domain belongs to. + """ + The organization that this domain belongs to. + """ organizations( - # Ordering options for verified organization connections. + """ + Ordering options for verified organization connections. + """ orderBy: VerifiedOrganizationOrder - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): VerifiedOrganizationConnection } - # A connection to a list of items. + """ + A connection to a list of items. + """ type VerifiedOrganizationConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [VerifiedOrganizationEdge] - # The total amount of verified organizations. + """ + The total amount of verified organizations. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type VerifiedOrganizationEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: VerifiedOrganization - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # Verified Organization object containing information for a given Organization. + """ + Verified Organization object containing information for a given Organization. + """ type VerifiedOrganization implements Node { - # The ID of an object + """ + The ID of an object + """ id: ID! - # The organizations acronym. + """ + The organizations acronym. + """ acronym: Acronym - # The full name of the organization. + """ + The full name of the organization. + """ name: String - # Slugified name of the organization. + """ + Slugified name of the organization. + """ slug: Slug - # The zone which the organization belongs to. + """ + The zone which the organization belongs to. + """ zone: String - # The sector which the organization belongs to. + """ + The sector which the organization belongs to. + """ sector: String - # The country in which the organization resides. + """ + The country in which the organization resides. + """ country: String - # The province in which the organization resides. + """ + The province in which the organization resides. + """ province: String - # The city in which the organization resides. + """ + The city in which the organization resides. + """ city: String - # Wether the organization is a verified organization. + """ + Whether the organization is a verified organization. + """ verified: Boolean - # Summaries based on scan types that are preformed on the given organizations domains. + """ + Summaries based on scan types that are preformed on the given organizations domains. + """ summaries: OrganizationSummary - # The number of domains associated with this organization. + """ + The number of domains associated with this organization. + """ domainCount: Int - # The domains which are associated with this organization. + """ + The domains which are associated with this organization. + """ domains( - # Ordering options for verified domain connections. + """ + Ordering options for verified domain connections. + """ orderBy: VerifiedDomainOrder - # Returns the items in the list that come after the specified cursor. + """ + Returns the items in the list that come after the specified cursor. + """ after: String - # Returns the first n items from the list. + """ + Returns the first n items from the list. + """ first: Int - # Returns the items in the list that come before the specified cursor. + """ + Returns the items in the list that come before the specified cursor. + """ before: String - # Returns the last n items from the list. + """ + Returns the last n items from the list. + """ last: Int ): VerifiedDomainConnection } - # A connection to a list of items. + """ + A connection to a list of items. + """ type VerifiedDomainConnection { - # Information to aid in pagination. + """ + Information to aid in pagination. + """ pageInfo: PageInfo! - # A list of edges. + """ + A list of edges. + """ edges: [VerifiedDomainEdge] - # The total amount of verified domains. + """ + The total amount of verified domains. + """ totalCount: Int } - # An edge in a connection. + """ + An edge in a connection. + """ type VerifiedDomainEdge { - # The item at the end of the edge + """ + The item at the end of the edge + """ node: VerifiedDomain - # A cursor for use in pagination + """ + A cursor for use in pagination + """ cursor: String! } - # Ordering options for verified domain connections. + """ + Ordering options for verified domain connections. + """ input VerifiedDomainOrder { - # The field to order verified domains by. + """ + The field to order verified domains by. + """ field: VerifiedDomainOrderField! - # The ordering direction. + """ + The ordering direction. + """ direction: OrderDirection! } - # Properties by which verified domain connections can be ordered. + """ + Properties by which verified domain connections can be ordered. + """ enum VerifiedDomainOrderField { - # Order verified domain edges by domain. + """ + Order verified domain edges by domain. + """ DOMAIN - # Order verified domain edges by last ran. + """ + Order verified domain edges by last ran. + """ LAST_RAN - # Order verified domain edges by dkim status. + """ + Order verified domain edges by dkim status. + """ DKIM_STATUS - # Order verified domain edges by dmarc status. + """ + Order verified domain edges by dmarc status. + """ DMARC_STATUS - # Order verified domain edges by https status. + """ + Order verified domain edges by https status. + """ HTTPS_STATUS - # Order verified domain edges by spf status. + """ + Order verified domain edges by spf status. + """ SPF_STATUS - # Order verified domain edges by ssl status. + """ + Order verified domain edges by ssl status. + """ SSL_STATUS } - # Ordering options for verified organization connections. + """ + Ordering options for verified organization connections. + """ input VerifiedOrganizationOrder { - # The field to order verified organizations by. + """ + The field to order verified organizations by. + """ field: VerifiedOrganizationOrderField! - # The ordering direction. + """ + The ordering direction. + """ direction: OrderDirection! } - # Properties by which verified organization connections can be ordered. + """ + Properties by which verified organization connections can be ordered. + """ enum VerifiedOrganizationOrderField { - # Order verified organization edges by acronym. + """ + Order verified organization edges by acronym. + """ ACRONYM - # Order verified organization edges by name. + """ + Order verified organization edges by name. + """ NAME - # Order verified organization edges by zone. + """ + Order verified organization edges by zone. + """ ZONE - # Order verified organization edges by sector. + """ + Order verified organization edges by sector. + """ SECTOR - # Order verified organization edges by country. + """ + Order verified organization edges by country. + """ COUNTRY - # Order verified organizations by summary mail pass count. + """ + Order verified organizations by summary mail pass count. + """ SUMMARY_MAIL_PASS - # Order verified organizations by summary mail fail count. + """ + Order verified organizations by summary mail fail count. + """ SUMMARY_MAIL_FAIL - # Order verified organizations by summary mail total count. + """ + Order verified organizations by summary mail total count. + """ SUMMARY_MAIL_TOTAL - # Order verified organizations by summary web pass count. + """ + Order verified organizations by summary web pass count. + """ SUMMARY_WEB_PASS - # Order verified organizations by summary web fail count. + """ + Order verified organizations by summary web fail count. + """ SUMMARY_WEB_FAIL - # Order verified organizations by summary web total count. + """ + Order verified organizations by summary web total count. + """ SUMMARY_WEB_TOTAL - # Order verified organizations by domain count. + """ + Order verified organizations by domain count. + """ DOMAIN_COUNT } type Mutation { - # This mutation allows admins and higher to invite users to any of their - # organizations, if the invited user does not have an account, they will be - # able to sign-up and be assigned to that organization in one mutation. + """ + This mutation allows admins and higher to invite users to any of their + organizations, if the invited user does not have an account, they will be + able to sign-up and be assigned to that organization in one mutation. + """ inviteUserToOrg(input: InviteUserToOrgInput!): InviteUserToOrgPayload - # This mutation allows users to leave a given organization. + """ + This mutation allows users to leave a given organization. + """ leaveOrganization(input: LeaveOrganizationInput!): LeaveOrganizationPayload - # This mutation allows users to close their account. - closeAccount(input: CloseAccountInput!): CloseAccountPayload - - # This mutation allows admins or higher to remove users from any organizations they belong to. + """ + This mutation allows admins or higher to remove users from any organizations they belong to. + """ removeUserFromOrg(input: RemoveUserFromOrgInput!): RemoveUserFromOrgPayload - # This mutation allows super admins, and admins of the given organization to - # update the permission level of a given user that already belongs to the - # given organization. + """ + This mutation allows users to request to join an organization. + """ + requestOrgAffiliation(input: RequestOrgAffiliationInput!): RequestOrgAffiliationPayload + + """ + This mutation allows a user to transfer org ownership to another user in the given org. + """ + transferOrgOwnership(input: TransferOrgOwnershipInput!): TransferOrgOwnershipPayload + + """ + This mutation allows super admins, and admins of the given organization to + update the permission level of a given user that already belongs to the + given organization. + """ updateUserRole(input: UpdateUserRoleInput!): UpdateUserRolePayload - # Mutation used to create a new domain for an organization. + """ + Mutation used to create multiple new domains for an organization. + """ + addOrganizationsDomains(input: AddOrganizationsDomainsInput!): AddOrganizationsDomainsPayload + + """ + Mutation used to create a new domain for an organization. + """ createDomain(input: CreateDomainInput!): CreateDomainPayload - # This mutation allows the removal of unused domains. + """ + Mutation to add domain to user's personal myTracker view. + """ + favouriteDomain(input: FavouriteDomainInput!): FavouriteDomainPayload + + """ + This mutation allows the removal of unused domains. + """ removeDomain(input: RemoveDomainInput!): RemoveDomainPayload - # This mutation is used to step a manual scan on a requested domain. - requestScan(input: RequestScanInput!): RequestScanPayload + """ + This mutation allows the removal of unused domains. + """ + removeOrganizationsDomains(input: RemoveOrganizationsDomainsInput!): RemoveOrganizationsDomainsPayload - # Mutation allows the modification of domains if domain is updated through out its life-cycle - updateDomain(input: UpdateDomainInput!): UpdateDomainPayload + """ + This mutation is used to start a subdomain discovery scan on a requested domain. + """ + requestDiscovery(input: RequestDiscoveryInput!): RequestDiscoveryPayload - # This mutation allows the creation of an organization inside the database. - createOrganization( - input: CreateOrganizationInput! - ): CreateOrganizationPayload + """ + This mutation is used to start a manual scan on a requested domain. + """ + requestScan(input: RequestScanInput!): RequestScanPayload + + """ + Mutation to remove domain from user's personal myTracker view. + """ + unfavouriteDomain(input: UnfavouriteDomainInput!): UnfavouriteDomainPayload - # This mutation allows the removal of unused organizations. - removeOrganization( - input: RemoveOrganizationInput! - ): RemoveOrganizationPayload + """ + Mutation allows the modification of domains if domain is updated through out its life-cycle + """ + updateDomain(input: UpdateDomainInput!): UpdateDomainPayload - # Mutation allows the modification of organizations if any changes to the organization may occur. - updateOrganization( - input: UpdateOrganizationInput! - ): UpdateOrganizationPayload + """ + This mutation allows the archival of unused organizations. + """ + archiveOrganization(input: ArchiveOrganizationInput!): ArchiveOrganizationPayload + + """ + This mutation allows the creation of an organization inside the database. + """ + createOrganization(input: CreateOrganizationInput!): CreateOrganizationPayload + + """ + This mutation allows the removal of unused organizations. + """ + removeOrganization(input: RemoveOrganizationInput!): RemoveOrganizationPayload + + """ + Mutation allows the modification of organizations if any changes to the organization may occur. + """ + updateOrganization(input: UpdateOrganizationInput!): UpdateOrganizationPayload + + """ + Mutation allows the verification of an organization. + """ + verifyOrganization(input: VerifyOrganizationInput!): VerifyOrganizationPayload + + """ + This mutation allows users to give their credentials and retrieve a token that gives them access to restricted content. + """ + authenticate(input: AuthenticateInput!): AuthenticatePayload - # Mutation allows the verification of an organization. - verifyOrganization( - input: VerifyOrganizationInput! - ): VerifyOrganizationPayload + """ + This mutation allows a super admin to close another user's account. + """ + closeAccountOther(input: CloseAccountOtherInput!): CloseAccountOtherPayload - # This mutation allows users to give their credentials and retrieve a token that gives them access to restricted content. - authenticate(input: AuthenticateInput!): AuthenticatePayload + """ + This mutation allows a user to close their account. + """ + closeAccountSelf(input: CloseAccountSelfInput!): CloseAccountSelfPayload - # This mutation allows users to give their current auth token, and refresh token, and receive a freshly updated auth token. + """ + This mutation allows users to give their current auth token, and refresh token, and receive a freshly updated auth token. + """ refreshTokens(input: RefreshTokensInput!): RefreshTokensPayload - # This mutation allows for users to remove a phone number from their account. + """ + This mutation allows for users to remove a phone number from their account. + """ removePhoneNumber(input: RemovePhoneNumberInput!): RemovePhoneNumberPayload - # This mutation allows the user to take the token they received in their email to reset their password. + """ + This mutation allows the user to take the token they received in their email to reset their password. + """ resetPassword(input: ResetPasswordInput!): ResetPasswordPayload - # This mutation is used for re-sending a verification email if it failed during user creation. - sendEmailVerification( - input: SendEmailVerificationInput! - ): SendEmailVerificationPayload + """ + This mutation is used for re-sending a verification email if it failed during user creation. + """ + sendEmailVerification(input: SendEmailVerificationInput!): SendEmailVerificationPayload - # This mutation allows a user to provide their username and request that a password reset email be sent to their account with a reset token in a url. - sendPasswordResetLink( - input: SendPasswordResetLinkInput! - ): SendPasswordResetLinkPayload + """ + This mutation allows a user to provide their username and request that a password reset email be sent to their account with a reset token in a url. + """ + sendPasswordResetLink(input: SendPasswordResetLinkInput!): SendPasswordResetLinkPayload - # This mutation is used for setting a new phone number for a user, and sending a code for verifying the new number. + """ + This mutation is used for setting a new phone number for a user, and sending a code for verifying the new number. + """ setPhoneNumber(input: SetPhoneNumberInput!): SetPhoneNumberPayload - # This mutation allows users to give their credentials and either signed in, re-directed to the tfa auth page, or given an error. + """ + This mutation allows users to give their credentials and either signed in, re-directed to the tfa auth page, or given an error. + """ signIn(input: SignInInput!): SignInPayload - # This mutation allows a user to sign out, and clear their cookies. + """ + This mutation allows a user to sign out, and clear their cookies. + """ signOut(input: SignOutInput!): SignOutPayload - # This mutation allows for new users to sign up for our sites services. + """ + This mutation allows for new users to sign up for our sites services. + """ signUp(input: SignUpInput!): SignUpPayload - # This mutation allows the user to update their account password. - updateUserPassword( - input: UpdateUserPasswordInput! - ): UpdateUserPasswordPayload + """ + This mutation allows the user to update their account password. + """ + updateUserPassword(input: UpdateUserPasswordInput!): UpdateUserPasswordPayload - # This mutation allows the user to update their user profile to change various details of their current profile. + """ + This mutation allows the user to update their user profile to change various details of their current profile. + """ updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfilePayload - # This mutation allows the user to verify their account through a token sent in an email. + """ + This mutation allows the user to verify their account through a token sent in an email. + """ verifyAccount(input: VerifyAccountInput!): VerifyAccountPayload - # This mutation allows the user to two factor authenticate. + """ + This mutation allows the user to two factor authenticate. + """ verifyPhoneNumber(input: verifyPhoneNumberInput!): verifyPhoneNumberPayload } type InviteUserToOrgPayload { - # \`InviteUserToOrgUnion\` returning either a \`InviteUserToOrgResult\`, or \`InviteUserToOrgError\` object. + """ + 'InviteUserToOrgUnion' returning either a 'InviteUserToOrgResult', or 'InviteUserToOrgError' object. + """ result: InviteUserToOrgUnion clientMutationId: String } - # This union is used with the \`InviteUserToOrg\` mutation, allowing for users to invite user to their org, and support any errors that may occur + """ + This union is used with the 'InviteUserToOrg' mutation, allowing for users to invite user to their org, and support any errors that may occur + """ union InviteUserToOrgUnion = AffiliationError | InviteUserToOrgResult - # This object is used to inform the user if any errors occurred while executing affiliation mutations. + """ + This object is used to inform the user if any errors occurred while executing affiliation mutations. + """ type AffiliationError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user of the invitation status. + """ + This object is used to inform the user of the invitation status. + """ type InviteUserToOrgResult { - # Informs the user if the invite or invite email was successfully sent. + """ + Informs the user if the invite or invite email was successfully sent. + """ status: String } input InviteUserToOrgInput { - # Users email that you would like to invite to your org. + """ + Users email that you would like to invite to your org. + """ userName: EmailAddress! - # The role which you would like this user to have. - requestedRole: RoleEnums! + """ + The role which you would like this user to have. + """ + requestedRole: InvitationRoleEnums! - # The organization you wish to invite the user to. + """ + The organization you wish to invite the user to. + """ orgId: ID! - - # The language in which the email will be sent in. - preferredLang: LanguageEnums! clientMutationId: String } + """ + An enum used when inviting users to an organization to assign their role. + """ + enum InvitationRoleEnums { + """ + A user who has been given access to view an organization. + """ + USER + + """ + A user who has the same access as a user write account, but can define new user read/write accounts. + """ + ADMIN + + """ + A user who has the same access as an admin, but can define new admins, and delete the organization. + """ + OWNER + + """ + A user who has the same access as an admin, but can define new admins. + """ + SUPER_ADMIN + } + type LeaveOrganizationPayload { - # \`LeaveOrganizationUnion\` resolving to either a \`LeaveOrganizationResult\` or \`AffiliationError\`. + """ + 'LeaveOrganizationUnion' resolving to either a 'LeaveOrganizationResult' or 'AffiliationError'. + """ result: LeaveOrganizationUnion clientMutationId: String } - # This union is used with the \`leaveOrganization\` mutation, allowing for users to leave a given organization, and support any errors that may occur. + """ + This union is used with the 'leaveOrganization' mutation, allowing for users to leave a given organization, and support any errors that may occur. + """ union LeaveOrganizationUnion = AffiliationError | LeaveOrganizationResult - # This object is used to inform the user that they successfully left a given organization. + """ + This object is used to inform the user that they successful left a given organization. + """ type LeaveOrganizationResult { - # Status message confirming the user left the org. + """ + Status message confirming the user left the org. + """ status: String } input LeaveOrganizationInput { - # Id of the organization the user is looking to leave. + """ + Id of the organization the user is looking to leave. + """ orgId: ID! clientMutationId: String } - type CloseAccountPayload { - # \`CloseAccountUnion\` resolving to either a \`CloseAccountResult\` or \`CloseAccountError\`. - result: CloseAccountUnion + type RemoveUserFromOrgPayload { + """ + 'RemoveUserFromOrgUnion' returning either a 'RemoveUserFromOrgResult', or 'RemoveUserFromOrgError' object. + """ + result: RemoveUserFromOrgUnion clientMutationId: String } - # This union is used with the \`CloseAccount\` mutation, allowing for users to close their account, and support any errors that may occur. - union CloseAccountUnion = CloseAccountError | CloseAccountResult + """ + This union is used with the 'RemoveUserFromOrg' mutation, allowing for users to remove a user from their org, and support any errors that may occur + """ + union RemoveUserFromOrgUnion = AffiliationError | RemoveUserFromOrgResult - # This object is used to inform the user if any errors occurred during closure of an account. - type CloseAccountError { - # Error code to inform user what the issue is related to. - code: Int + """ + This object is used to inform the user of the removal status. + """ + type RemoveUserFromOrgResult { + """ + Informs the user if the user was successfully removed. + """ + status: String - # Description of the issue that was encountered. - description: String + """ + The user that was just removed. + """ + user: SharedUser } - # This object is used to inform the user that they successfully closed their account. - type CloseAccountResult { - # Status message confirming the user has closed their account. - status: String + input RemoveUserFromOrgInput { + """ + The user id of the user to be removed. + """ + userId: ID! + + """ + The organization that the user is to be removed from. + """ + orgId: ID! + clientMutationId: String } - input CloseAccountInput { - # Id of the user who is closing their account. - userId: ID + type RequestOrgAffiliationPayload { + """ + 'InviteUserToOrgUnion' returning either a 'InviteUserToOrgResult', or 'InviteUserToOrgError' object. + """ + result: InviteUserToOrgUnion + clientMutationId: String } - type RemoveUserFromOrgPayload { - # \`RemoveUserFromOrgUnion\` returning either a \`RemoveUserFromOrgResult\`, or \`RemoveUserFromOrgError\` object. - result: RemoveUserFromOrgUnion + input RequestOrgAffiliationInput { + """ + The organization you wish to invite the user to. + """ + orgId: ID! clientMutationId: String } - # This union is used with the \`RemoveUserFromOrg\` mutation, allowing for users to remove a user from their org, and support any errors that may occur - union RemoveUserFromOrgUnion = AffiliationError | RemoveUserFromOrgResult + type TransferOrgOwnershipPayload { + """ + 'TransferOrgOwnershipUnion' resolving to either a 'TransferOrgOwnershipResult' or 'AffiliationError'. + """ + result: TransferOrgOwnershipUnion + clientMutationId: String + } - # This object is used to inform the user of the removal status. - type RemoveUserFromOrgResult { - # Informs the user if the user was successfully removed. - status: String + """ + This union is used with the 'transferOrgOwnership' mutation, allowing for + users to transfer ownership of a given organization, and support any errors that may occur. + """ + union TransferOrgOwnershipUnion = AffiliationError | TransferOrgOwnershipResult - # The user that was just removed. - user: SharedUser + """ + This object is used to inform the user that they successful transferred ownership of a given organization. + """ + type TransferOrgOwnershipResult { + """ + Status message confirming the user transferred ownership of the org. + """ + status: String } - input RemoveUserFromOrgInput { - # The user id of the user to be removed. - userId: ID! - - # The organization that the user is to be removed from. + input TransferOrgOwnershipInput { + """ + Id of the organization the user is looking to transfer ownership of. + """ orgId: ID! + + """ + Id of the user that the org ownership is being transferred to. + """ + userId: ID! clientMutationId: String } type UpdateUserRolePayload { - # \`UpdateUserRoleUnion\` returning either a \`UpdateUserRoleResult\`, or \`UpdateUserRoleError\` object. + """ + 'UpdateUserRoleUnion' returning either a 'UpdateUserRoleResult', or 'UpdateUserRoleError' object. + """ result: UpdateUserRoleUnion clientMutationId: String } - # This union is used with the \`UpdateUserRole\` mutation, allowing for users to update a users role in an org, and support any errors that may occur + """ + This union is used with the 'UpdateUserRole' mutation, allowing for users to update a users role in an org, and support any errors that may occur + """ union UpdateUserRoleUnion = AffiliationError | UpdateUserRoleResult - # This object is used to inform the user of the status of the role update. + """ + This object is used to inform the user of the status of the role update. + """ type UpdateUserRoleResult { - # Informs the user if the user was successfully removed. + """ + Informs the user if the user who's role was successfully updated. + """ status: String + + """ + The user who's role was successfully updated. + """ + user: SharedUser } input UpdateUserRoleInput { - # The username of the user you wish to update their role to. + """ + The username of the user you wish to update their role to. + """ userName: EmailAddress! - # The organization that the admin, and the user both belong to. + """ + The organization that the admin, and the user both belong to. + """ orgId: ID! - # The role that the admin wants to give to the selected user. + """ + The role that the admin wants to give to the selected user. + """ role: RoleEnums! clientMutationId: String } - type CreateDomainPayload { - # \`CreateDomainUnion\` returning either a \`Domain\`, or \`CreateDomainError\` object. - result: CreateDomainUnion + type AddOrganizationsDomainsPayload { + """ + 'BulkModifyDomainsUnion' returning either a 'DomainBulkResult', or 'DomainErrorType' object. + """ + result: BulkModifyDomainsUnion clientMutationId: String } - # This union is used with the \`CreateDomain\` mutation, - # allowing for users to create a domain and add it to their org, - # and support any errors that may occur - union CreateDomainUnion = DomainError | Domain + """ + This union is used with the 'AddOrganizationsDomains' and 'RemoveOrganizationsDomains' mutation, + allowing for users to add/remove multiple domains belonging to their org, + and support any errors that may occur + """ + union BulkModifyDomainsUnion = DomainError | DomainBulkResult - # This object is used to inform the user if any errors occurred while using a domain mutation. + """ + This object is used to inform the user if any errors occurred while using a domain mutation. + """ type DomainError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } + """ + This object is used to inform the user that no errors were encountered while mutating a domain. + """ + type DomainBulkResult { + """ + Informs the user if the domain mutation was successful. + """ + status: String + } + + input AddOrganizationsDomainsInput { + """ + The global id of the organization you wish to assign this domain to. + """ + orgId: ID! + + """ + Url that you would like to be added to the database. + """ + domains: [DomainScalar]! + + """ + New domains will be hidden. + """ + hideNewDomains: Boolean + + """ + New domains will be tagged with NEW. + """ + tagNewDomains: Boolean + + """ + New domains will be tagged with STAGING. + """ + tagStagingDomains: Boolean + + """ + Audit logs will be created. + """ + audit: Boolean + clientMutationId: String + } + + type CreateDomainPayload { + """ + 'CreateDomainUnion' returning either a 'Domain', or 'CreateDomainError' object. + """ + result: CreateDomainUnion + clientMutationId: String + } + + """ + This union is used with the 'CreateDomain' mutation, + allowing for users to create a domain and add it to their org, + and support any errors that may occur + """ + union CreateDomainUnion = DomainError | Domain + input CreateDomainInput { - # The global id of the organization you wish to assign this domain to. + """ + The global id of the organization you wish to assign this domain to. + """ orgId: ID! - # Url that you would like to be added to the database. + """ + Url that you would like to be added to the database. + """ domain: DomainScalar! - # DKIM selector strings corresponding to this domain. - selectors: [Selector] + """ + DKIM selector strings corresponding to this domain. + """ + selectors: [SelectorInput] + + """ + List of labelled tags users have applied to the domain. + """ + tags: [InputTag] + + """ + Value that determines if the domain is excluded from an organization's score. + """ + hidden: Boolean + + """ + Value that determines if the domain is excluded from the scanning process. + """ + archived: Boolean + + """ + Comment describing reason for adding out-of-scope domain. + """ + outsideComment: OutsideDomainCommentEnum + clientMutationId: String + } + + """ + A field that conforms to a DKIM selector for input. Must be either a single asterisk or a string where only alphanumeric characters and periods are allowed, string must also start and end with alphanumeric characters + """ + scalar SelectorInput + + """ + User-generated tag assigned to domains for labeling and management. + """ + input InputTag { + """ + The English translation of the label. + """ + en: DomainTagLabel! + + """ + The French translation of the label. + """ + fr: DomainTagLabel! + } + + """ + An enum used to assign and test user-generated domain tags + """ + enum DomainTagLabel { + """ + English label for tagging domains as new to the system. + """ + NEW + + """ + French label for tagging domains as new to the system. + """ + NOUVEAU + + """ + Bilingual Label for tagging domains as a production environment. + """ + PROD + + """ + English label for tagging domains as a staging environment. + """ + STAGING + + """ + French label for tagging domains as a staging environment. + """ + DEV + + """ + Bilingual label for tagging domains as a test environment. + """ + TEST + + """ + Bilingual label for tagging domains as web-hosting. + """ + WEB + + """ + English label for tagging domains that are not active. + """ + INACTIVE + + """ + French label for tagging domains that are not active. + """ + INACTIF + + """ + English label for tagging domains that are hidden. + """ + HIDDEN + + """ + English label for tagging domains that are archived. + """ + ARCHIVED + + """ + Label for tagging domains that have an rcode status of NXDOMAIN. + """ + NXDOMAIN + + """ + Label for tagging domains that are possibly blocked by a firewall. + """ + BLOCKED + + """ + Label for tagging domains that have a wildcard sibling. + """ + WILDCARD_SIBLING + + """ + Label for tagging domains that have a pending web scan. + """ + SCAN_PENDING + + """ + English label for tagging domains that are outside the scope of the project. + """ + OUTSIDE + + """ + French label for tagging domains that are outside the scope of the project. + """ + EXTERIEUR + } + + """ + Reason why an outside domain was added to the organization. + """ + enum OutsideDomainCommentEnum { + """ + Organization is invested in the outside domain. + """ + INVESTMENT + + """ + Organization owns this domain, but it is outside the allowed scope. + """ + OWNERSHIP + + """ + Other reason. + """ + OTHER + } + + type FavouriteDomainPayload { + """ + 'CreateDomainUnion' returning either a 'Domain', or 'CreateDomainError' object. + """ + result: CreateDomainUnion + clientMutationId: String + } + + input FavouriteDomainInput { + """ + The global id of the domain you wish to favourite. + """ + domainId: ID! clientMutationId: String } type RemoveDomainPayload { - # \`RemoveDomainUnion\` returning either a \`DomainResultType\`, or \`DomainErrorType\` object. + """ + 'RemoveDomainUnion' returning either a 'DomainResultType', or 'DomainErrorType' object. + """ result: RemoveDomainUnion! clientMutationId: String } - # This union is used with the \`RemoveDomain\` mutation, - # allowing for users to remove a domain belonging to their org, - # and support any errors that may occur + """ + This union is used with the 'RemoveDomain' mutation, + allowing for users to remove a domain belonging to their org, + and support any errors that may occur + """ union RemoveDomainUnion = DomainError | DomainResult - # This object is used to inform the user that no errors were encountered while removing a domain. + """ + This object is used to inform the user that no errors were encountered while mutating a domain. + """ type DomainResult { - # Informs the user if the domain removal was successful. + """ + Informs the user if the domain mutation was successful. + """ status: String - # The domain that is being mutated. + """ + The domain that is being mutated. + """ domain: Domain } input RemoveDomainInput { - # The global id of the domain you wish to remove. + """ + The global id of the domain you wish to remove. + """ domainId: ID! - # The organization you wish to remove the domain from. + """ + The organization you wish to remove the domain from. + """ + orgId: ID! + + """ + The reason given for why this domain is being removed from the organization. + """ + reason: DomainRemovalReasonEnum! + clientMutationId: String + } + + type RemoveOrganizationsDomainsPayload { + """ + 'BulkModifyDomainsUnion' returning either a 'DomainBulkResult', or 'DomainErrorType' object. + """ + result: BulkModifyDomainsUnion! + clientMutationId: String + } + + input RemoveOrganizationsDomainsInput { + """ + Domains you wish to remove from the organization. + """ + domains: [DomainScalar]! + + """ + The organization you wish to remove the domain from. + """ + orgId: ID! + + """ + Domains will be archived. + """ + archiveDomains: Boolean + + """ + Audit logs will be created. + """ + audit: Boolean + clientMutationId: String + } + + type RequestDiscoveryPayload { + """ + Informs the user if the scan was dispatched successfully. + """ + status: String + clientMutationId: String + } + + input RequestDiscoveryInput { + """ + The base domain that the subdomain scan will be ran on. + """ + domain: DomainScalar + + """ + The global id of the organization you wish to assign new found domains to. + """ orgId: ID! clientMutationId: String } type RequestScanPayload { - # Informs the user if the scan was dispatched successfully. + """ + Informs the user if the scan was dispatched successfully. + """ status: String clientMutationId: String } input RequestScanInput { - # The domain that the scan will be ran on. + """ + The domain that the scan will be ran on. + """ domain: DomainScalar clientMutationId: String } + type UnfavouriteDomainPayload { + """ + 'RemoveDomainUnion' returning either a 'DomainResultType', or 'DomainErrorType' object. + """ + result: RemoveDomainUnion! + clientMutationId: String + } + + input UnfavouriteDomainInput { + """ + The global id of the domain you wish to favourite. + """ + domainId: ID! + clientMutationId: String + } + type UpdateDomainPayload { - # \`UpdateDomainUnion\` returning either a \`Domain\`, or \`DomainError\` object. + """ + 'UpdateDomainUnion' returning either a 'Domain', or 'DomainError' object. + """ result: UpdateDomainUnion clientMutationId: String } - # This union is used with the \`UpdateDomain\` mutation, - # allowing for users to update a domain belonging to their org, - # and support any errors that may occur + """ + This union is used with the 'UpdateDomain' mutation, + allowing for users to update a domain belonging to their org, + and support any errors that may occur + """ union UpdateDomainUnion = DomainError | Domain input UpdateDomainInput { - # The global id of the domain that is being updated. + """ + The global id of the domain that is being updated. + """ domainId: ID! - # The global ID of the organization used for permission checks. + """ + The global ID of the organization used for permission checks. + """ orgId: ID! - # The new url of the of the old domain. + """ + The new url of the of the old domain. + """ domain: DomainScalar - # The updated DKIM selector strings corresponding to this domain. - selectors: [Selector] + """ + The updated DKIM selector strings corresponding to this domain. + """ + selectors: [SelectorInput] + + """ + List of labelled tags users have applied to the domain. + """ + tags: [InputTag] + + """ + Value that determines if the domain is excluded from an organization's score. + """ + hidden: Boolean + + """ + Value that determines if the domain is excluded from the scanning process. + """ + archived: Boolean + + """ + Comment describing reason for adding out-of-scope domain. + """ + outsideComment: OutsideDomainCommentEnum clientMutationId: String } - type CreateOrganizationPayload { - # \`CreateOrganizationUnion\` returning either an \`Organization\`, or \`OrganizationError\` object. - result: CreateOrganizationUnion + type ArchiveOrganizationPayload { + """ + 'RemoveOrganizationUnion' returning either an 'OrganizationResult', or 'OrganizationError' object. + """ + result: RemoveOrganizationUnion! clientMutationId: String } - # This union is used with the \`CreateOrganization\` mutation, - # allowing for users to create an organization, and support any errors that may occur - union CreateOrganizationUnion = OrganizationError | Organization + """ + This union is used with the 'RemoveOrganization' mutation, + allowing for users to remove an organization they belong to, + and support any errors that may occur + """ + union RemoveOrganizationUnion = OrganizationError | OrganizationResult - # This object is used to inform the user if any errors occurred while using an organization mutation. + """ + This object is used to inform the user if any errors occurred while using an organization mutation. + """ type OrganizationError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } + """ + This object is used to inform the user that no errors were encountered while running organization mutations. + """ + type OrganizationResult { + """ + Informs the user if the organization mutation was successful. + """ + status: String + + """ + The organization that was being affected by the mutation. + """ + organization: Organization + } + + input ArchiveOrganizationInput { + """ + The global id of the organization you wish you archive. + """ + orgId: ID! + clientMutationId: String + } + + type CreateOrganizationPayload { + """ + 'CreateOrganizationUnion' returning either an 'Organization', or 'OrganizationError' object. + """ + result: CreateOrganizationUnion + clientMutationId: String + } + + """ + This union is used with the 'CreateOrganization' mutation, + allowing for users to create an organization, and support any errors that may occur + """ + union CreateOrganizationUnion = OrganizationError | Organization + input CreateOrganizationInput { - # The English acronym of the organization. + """ + The English acronym of the organization. + """ acronymEN: Acronym! - # The French acronym of the organization. + """ + The French acronym of the organization. + """ acronymFR: Acronym! - # The English name of the organization. + """ + The English name of the organization. + """ nameEN: String! - # The French name of the organization. + """ + The French name of the organization. + """ nameFR: String! - # The English translation of the zone the organization belongs to. + """ + The English translation of the zone the organization belongs to. + """ zoneEN: String! - # The English translation of the zone the organization belongs to. + """ + The English translation of the zone the organization belongs to. + """ zoneFR: String! - # The English translation of the sector the organization belongs to. + """ + The English translation of the sector the organization belongs to. + """ sectorEN: String! - # The French translation of the sector the organization belongs to. + """ + The French translation of the sector the organization belongs to. + """ sectorFR: String! - # The English translation of the country the organization resides in. + """ + The English translation of the country the organization resides in. + """ countryEN: String! - # The French translation of the country the organization resides in. + """ + The French translation of the country the organization resides in. + """ countryFR: String! - # The English translation of the province the organization resides in. + """ + The English translation of the province the organization resides in. + """ provinceEN: String! - # The French translation of the province the organization resides in. + """ + The French translation of the province the organization resides in. + """ provinceFR: String! - # The English translation of the city the organization resides in. + """ + The English translation of the city the organization resides in. + """ cityEN: String! - # The French translation of the city the organization resides in. + """ + The French translation of the city the organization resides in. + """ cityFR: String! clientMutationId: String } type RemoveOrganizationPayload { - # \`RemoveOrganizationUnion\` returning either an \`OrganizationResult\`, or \`OrganizationError\` object. + """ + 'RemoveOrganizationUnion' returning either an 'OrganizationResult', or 'OrganizationError' object. + """ result: RemoveOrganizationUnion! clientMutationId: String } - # This union is used with the \`RemoveOrganization\` mutation, - # allowing for users to remove an organization they belong to, - # and support any errors that may occur - union RemoveOrganizationUnion = OrganizationError | OrganizationResult - - # This object is used to inform the user that no errors were encountered while running organization mutations. - type OrganizationResult { - # Informs the user if the organization mutation was successful. - status: String - - # The organization that was being affected by the mutation. - organization: Organization - } - input RemoveOrganizationInput { - # The global id of the organization you wish you remove. + """ + The global id of the organization you wish you remove. + """ orgId: ID! clientMutationId: String } type UpdateOrganizationPayload { - # \`UpdateOrganizationUnion\` returning either an \`Organization\`, or \`OrganizationError\` object. + """ + 'UpdateOrganizationUnion' returning either an 'Organization', or 'OrganizationError' object. + """ result: UpdateOrganizationUnion! clientMutationId: String } - # This union is used with the \`UpdateOrganization\` mutation, - # allowing for users to update an organization, and support any errors that may occur + """ + This union is used with the 'UpdateOrganization' mutation, + allowing for users to update an organization, and support any errors that may occur + """ union UpdateOrganizationUnion = OrganizationError | Organization input UpdateOrganizationInput { - # The global id of the organization to be updated. + """ + The global id of the organization to be updated. + """ id: ID! - # The English acronym of the organization. + """ + The English acronym of the organization. + """ acronymEN: Acronym - # The French acronym of the organization. + """ + The French acronym of the organization. + """ acronymFR: Acronym - # The English name of the organization. + """ + The English name of the organization. + """ nameEN: String - # The French name of the organization. + """ + The French name of the organization. + """ nameFR: String - # The English translation of the zone the organization belongs to. + """ + The English translation of the zone the organization belongs to. + """ zoneEN: String - # The English translation of the zone the organization belongs to. + """ + The English translation of the zone the organization belongs to. + """ zoneFR: String - # The English translation of the sector the organization belongs to. + """ + The English translation of the sector the organization belongs to. + """ sectorEN: String - # The French translation of the sector the organization belongs to. + """ + The French translation of the sector the organization belongs to. + """ sectorFR: String - # The English translation of the country the organization resides in. + """ + The English translation of the country the organization resides in. + """ countryEN: String - # The French translation of the country the organization resides in. + """ + The French translation of the country the organization resides in. + """ countryFR: String - # The English translation of the province the organization resides in. + """ + The English translation of the province the organization resides in. + """ provinceEN: String - # The French translation of the province the organization resides in. + """ + The French translation of the province the organization resides in. + """ provinceFR: String - # The English translation of the city the organization resides in. + """ + The English translation of the city the organization resides in. + """ cityEN: String - # The French translation of the city the organization resides in. + """ + The French translation of the city the organization resides in. + """ cityFR: String + + """ + If the organization has domains that are managed externally. + """ + externallyManaged: Boolean clientMutationId: String } type VerifyOrganizationPayload { - # \`VerifyOrganizationUnion\` returning either an \`OrganizationResult\`, or \`OrganizationError\` object. + """ + 'VerifyOrganizationUnion' returning either an 'OrganizationResult', or 'OrganizationError' object. + """ result: VerifyOrganizationUnion clientMutationId: String } - # This union is used with the \`VerifyOrganization\` mutation, - # allowing for super admins to verify an organization, - # and support any errors that may occur + """ + This union is used with the 'VerifyOrganization' mutation, + allowing for super admins to verify an organization, + and support any errors that may occur + """ union VerifyOrganizationUnion = OrganizationError | OrganizationResult input VerifyOrganizationInput { - # The global id of the organization to be verified. + """ + The global id of the organization to be verified. + """ orgId: ID! clientMutationId: String } type AuthenticatePayload { - # Authenticate union returning either a \`authResult\` or \`authenticateError\` object. + """ + Authenticate union returning either a 'authResult' or 'authenticateError' object. + """ result: AuthenticateUnion clientMutationId: String } - # This union is used with the \`authenticate\` mutation, allowing for the user to authenticate, and support any errors that may occur + """ + This union is used with the 'authenticate' mutation, allowing for the user to authenticate, and support any errors that may occur + """ union AuthenticateUnion = AuthResult | AuthenticateError - # An object used to return information when users sign up or authenticate. + """ + An object used to return information when users sign up or authenticate. + """ type AuthResult { - # JWT used for accessing controlled content. + """ + JWT used for accessing controlled content. + """ authToken: String - # User that has just been created or signed in. + """ + User that has just been created or signed in. + """ user: PersonalUser } - # This object is used to inform the user if any errors occurred during authentication. + """ + This object is used to inform the user if any errors occurred during authentication. + """ type AuthenticateError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } input AuthenticateInput { - # Security code found in text msg, or email inbox. + """ + The method that the user wants to receive their authentication code by. + """ + sendMethod: TFASendMethodEnum! + + """ + Security code found in text msg, or email inbox. + """ authenticationCode: Int! - # The JWT that is retrieved from the sign in mutation. + """ + The JWT that is retrieved from the sign in mutation. + """ authenticateToken: String! clientMutationId: String } + type CloseAccountOtherPayload { + """ + 'CloseAccountUnion' returning either a 'CloseAccountResult', or 'CloseAccountError' object. + """ + result: CloseAccountUnion + clientMutationId: String + } + + """ + This union is used for the 'closeAccount' mutation, to support successful or errors that may occur. + """ + union CloseAccountUnion = CloseAccountResult | CloseAccountError + + """ + This object is used to inform the user of the status of closing their account. + """ + type CloseAccountResult { + """ + Status of closing the users account. + """ + status: String + } + + """ + This object is used to inform the user if any errors occurred while closing their account. + """ + type CloseAccountError { + """ + Error code to inform user what the issue is related to. + """ + code: Int + + """ + Description of the issue encountered. + """ + description: String + } + + input CloseAccountOtherInput { + """ + The user id of a user you want to close the account of. + """ + userId: ID + clientMutationId: String + } + + type CloseAccountSelfPayload { + """ + 'CloseAccountUnion' returning either a 'CloseAccountResult', or 'CloseAccountError' object. + """ + result: CloseAccountUnion + clientMutationId: String + } + + input CloseAccountSelfInput { + clientMutationId: String + } + type RefreshTokensPayload { - # Refresh tokens union returning either a \`authResult\` or \`authenticateError\` object. + """ + Refresh tokens union returning either a 'authResult' or 'authenticateError' object. + """ result: RefreshTokensUnion clientMutationId: String } - # This union is used with the \`refreshTokens\` mutation, allowing for the user to refresh their tokens, and support any errors that may occur + """ + This union is used with the 'refreshTokens' mutation, allowing for the user to refresh their tokens, and support any errors that may occur + """ union RefreshTokensUnion = AuthResult | AuthenticateError input RefreshTokensInput { @@ -3067,28 +6096,40 @@ export const getTypeNames = () => gql` } type RemovePhoneNumberPayload { - # \`RemovePhoneNumberUnion\` returning either a \`RemovePhoneNumberResult\`, or \`RemovePhoneNumberError\` object. + """ + 'RemovePhoneNumberUnion' returning either a 'RemovePhoneNumberResult', or 'RemovePhoneNumberError' object. + """ result: RemovePhoneNumberUnion clientMutationId: String } - # This union is used with the \`RemovePhoneNumber\` mutation, allowing for users to remove their phone number, and support any errors that may occur - union RemovePhoneNumberUnion = - RemovePhoneNumberError - | RemovePhoneNumberResult + """ + This union is used with the 'RemovePhoneNumber' mutation, allowing for users to remove their phone number, and support any errors that may occur + """ + union RemovePhoneNumberUnion = RemovePhoneNumberError | RemovePhoneNumberResult - # This object is used to inform the user if any errors occurred while removing their phone number. + """ + This object is used to inform the user if any errors occurred while removing their phone number. + """ type RemovePhoneNumberError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while removing their phone number. + """ + This object is used to inform the user that no errors were encountered while removing their phone number. + """ type RemovePhoneNumberResult { - # Informs the user if the phone number removal was successful. + """ + Informs the user if the phone number removal was successful. + """ status: String } @@ -3097,139 +6138,209 @@ export const getTypeNames = () => gql` } type ResetPasswordPayload { - # \`ResetPasswordUnion\` returning either a \`ResetPasswordResult\`, or \`ResetPasswordError\` object. + """ + 'ResetPasswordUnion' returning either a 'ResetPasswordResult', or 'ResetPasswordError' object. + """ result: ResetPasswordUnion clientMutationId: String } - # This union is used with the \`ResetPassword\` mutation, allowing for users to reset their password, and support any errors that may occur + """ + This union is used with the 'ResetPassword' mutation, allowing for users to reset their password, and support any errors that may occur + """ union ResetPasswordUnion = ResetPasswordError | ResetPasswordResult - # This object is used to inform the user if any errors occurred while resetting their password. + """ + This object is used to inform the user if any errors occurred while resetting their password. + """ type ResetPasswordError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while resetting their password. + """ + This object is used to inform the user that no errors were encountered while resetting their password. + """ type ResetPasswordResult { - # Informs the user if the password reset was successful, and to redirect to sign in page. + """ + Informs the user if the password reset was successful, and to redirect to sign in page. + """ status: String } input ResetPasswordInput { - # The users new password. + """ + The users new password. + """ password: String! - # A confirmation password to confirm the new password. + """ + A confirmation password to confirm the new password. + """ confirmPassword: String! - # The JWT found in the url, redirected from the email they received. + """ + The JWT found in the url, redirected from the email they received. + """ resetToken: String! clientMutationId: String } type SendEmailVerificationPayload { - # Informs the user if the email was sent successfully. + """ + Informs the user if the email was sent successfully. + """ status: String clientMutationId: String } input SendEmailVerificationInput { - # The users email address used for sending the verification email. + """ + The users email address used for sending the verification email. + """ userName: EmailAddress! clientMutationId: String } type SendPasswordResetLinkPayload { - # Informs the user if the password reset email was sent successfully. + """ + Informs the user if the password reset email was sent successfully. + """ status: String clientMutationId: String } input SendPasswordResetLinkInput { - # User name for the account you would like to receive a password reset link for. + """ + User name for the account you would like to receive a password reset link for. + """ userName: EmailAddress! clientMutationId: String } type SetPhoneNumberPayload { - # \`SetPhoneNumberUnion\` returning either a \`SetPhoneNumberResult\`, or \`SetPhoneNumberError\` object. + """ + 'SetPhoneNumberUnion' returning either a 'SetPhoneNumberResult', or 'SetPhoneNumberError' object. + """ result: SetPhoneNumberUnion clientMutationId: String } - # This union is used with the \`setPhoneNumber\` mutation, allowing for users to send a verification code to their phone, and support any errors that may occur + """ + This union is used with the 'setPhoneNumber' mutation, allowing for users to send a verification code to their phone, and support any errors that may occur + """ union SetPhoneNumberUnion = SetPhoneNumberError | SetPhoneNumberResult - # This object is used to inform the user if any errors occurred while setting a new phone number. + """ + This object is used to inform the user if any errors occurred while setting a new phone number. + """ type SetPhoneNumberError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while setting a new phone number. + """ + This object is used to inform the user that no errors were encountered while setting a new phone number. + """ type SetPhoneNumberResult { - # Informs the user if their phone code was successfully sent. + """ + Informs the user if their phone code was successfully sent. + """ status: String - # The user who set their phone number. + """ + The user who set their phone number. + """ user: PersonalUser } input SetPhoneNumberInput { - # The phone number that the text message will be sent to. + """ + The phone number that the text message will be sent to. + """ phoneNumber: PhoneNumber! clientMutationId: String } type SignInPayload { - # \`SignInUnion\` returning either a \`regularSignInResult\`, \`tfaSignInResult\`, or \`signInError\` object. + """ + 'SignInUnion' returning either a 'regularSignInResult', 'tfaSignInResult', or 'signInError' object. + """ result: SignInUnion clientMutationId: String } - # This union is used with the \`SignIn\` mutation, allowing for multiple styles of logging in, and support any errors that may occur + """ + This union is used with the 'SignIn' mutation, allowing for multiple styles of logging in, and support any errors that may occur + """ union SignInUnion = AuthResult | SignInError | TFASignInResult - # This object is used to inform the user if any errors occurred during sign in. + """ + This object is used to inform the user if any errors occurred during sign in. + """ type SignInError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used when the user signs in and has validated either their email or phone. + """ + This object is used when the user signs in and has validated either their email or phone. + """ type TFASignInResult { - # Token used to verify during authentication. + """ + Token used to verify during authentication. + """ authenticateToken: String - # Wether the authentication code was sent through text, or email. + """ + Whether the authentication code was sent through text, or email. + """ sendMethod: String } input SignInInput { - # The email the user signed up with. + """ + The email the user signed up with. + """ userName: EmailAddress! - # The password the user signed up with + """ + The password the user signed up with + """ password: String! - # Whether or not the user wants to stay signed in after leaving the site. + """ + Whether or not the user wants to stay signed in after leaving the site. + """ rememberMe: Boolean = false clientMutationId: String } type SignOutPayload { - # Status of the users signing-out. + """ + Status of the users signing-out. + """ status: String clientMutationId: String } @@ -3239,411 +6350,297 @@ export const getTypeNames = () => gql` } type SignUpPayload { - # \`SignUpUnion\` returning either a \`AuthResult\`, or \`SignUpError\` object. + """ + 'SignUpUnion' returning either a 'TFASignInResult', or 'SignUpError' object. + """ result: SignUpUnion clientMutationId: String } - # This union is used with the \`signUp\` mutation, allowing for the user to sign up, and support any errors that may occur. - union SignUpUnion = AuthResult | SignUpError + """ + This union is used with the 'signUp' mutation, allowing for the user to sign up, and support any errors that may occur. + """ + union SignUpUnion = TFASignInResult | SignUpError - # This object is used to inform the user if any errors occurred during sign up. + """ + This object is used to inform the user if any errors occurred during sign up. + """ type SignUpError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } input SignUpInput { - # The name that will be displayed to other users. + """ + The name that will be displayed to other users. + """ displayName: String! - # Email address that the user will use to authenticate with. + """ + Email address that the user will use to authenticate with. + """ userName: EmailAddress! - # The password the user will authenticate with. + """ + The password the user will authenticate with. + """ password: String! - # A secondary password field used to confirm the user entered the correct password. + """ + A secondary password field used to confirm the user entered the correct password. + """ confirmPassword: String! - # The users preferred language. + """ + The users preferred language. + """ preferredLang: LanguageEnums! - # A token sent by email, that will assign a user to an organization with a pre-determined role. + """ + A token sent by email, that will assign a user to an organization with a pre-determined role. + """ signUpToken: String - # Whether or not the user wants to stay signed in after leaving the site. + """ + Whether or not the user wants to stay signed in after leaving the site. + """ rememberMe: Boolean = false clientMutationId: String } type UpdateUserPasswordPayload { - # \`UpdateUserPasswordUnion\` returning either a \`UpdateUserPasswordResultType\`, or \`UpdateUserPasswordError\` object. + """ + 'UpdateUserPasswordUnion' returning either a 'UpdateUserPasswordResultType', or 'UpdateUserPasswordError' object. + """ result: UpdateUserPasswordUnion clientMutationId: String } - # This union is used with the \`updateUserPassword\` mutation, allowing for users to update their password, and support any errors that may occur - union UpdateUserPasswordUnion = - UpdateUserPasswordError - | UpdateUserPasswordResultType + """ + This union is used with the 'updateUserPassword' mutation, allowing for users to update their password, and support any errors that may occur + """ + union UpdateUserPasswordUnion = UpdateUserPasswordError | UpdateUserPasswordResultType - # This object is used to inform the user if any errors occurred while updating their password. + """ + This object is used to inform the user if any errors occurred while updating their password. + """ type UpdateUserPasswordError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while updating their password. + """ + This object is used to inform the user that no errors were encountered while updating their password. + """ type UpdateUserPasswordResultType { - # Informs the user if their password was successfully updated. + """ + Informs the user if their password was successfully updated. + """ status: String } input UpdateUserPasswordInput { - # The users current password to verify it is the current user. + """ + The users current password to verify it is the current user. + """ currentPassword: String! - # The new password the user wishes to change to. + """ + The new password the user wishes to change to. + """ updatedPassword: String! - # A password confirmation of their new password. + """ + A password confirmation of their new password. + """ updatedPasswordConfirm: String! clientMutationId: String } type UpdateUserProfilePayload { - # \`UpdateUserProfileUnion\` returning either a \`UpdateUserProfileResult\`, or \`UpdateUserProfileError\` object. + """ + 'UpdateUserProfileUnion' returning either a 'UpdateUserProfileResult', or 'UpdateUserProfileError' object. + """ result: UpdateUserProfileUnion clientMutationId: String } - # This union is used with the \`updateUserProfile\` mutation, allowing for users to update their profile, and support any errors that may occur - union UpdateUserProfileUnion = - UpdateUserProfileError - | UpdateUserProfileResult + """ + This union is used with the 'updateUserProfile' mutation, allowing for users to update their profile, and support any errors that may occur + """ + union UpdateUserProfileUnion = UpdateUserProfileError | UpdateUserProfileResult - # This object is used to inform the user if any errors occurred while updating their profile. + """ + This object is used to inform the user if any errors occurred while updating their profile. + """ type UpdateUserProfileError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while resetting their password. + """ + This object is used to inform the user that no errors were encountered while resetting their password. + """ type UpdateUserProfileResult { - # Informs the user if the password reset was successful, and to redirect to sign in page. + """ + Informs the user if the password reset was successful, and to redirect to sign in page. + """ status: String - # Return the newly updated user information. + """ + Return the newly updated user information. + """ user: PersonalUser } input UpdateUserProfileInput { - # The updated display name the user wishes to change to. + """ + The updated display name the user wishes to change to. + """ displayName: String - # The updated user name the user wishes to change to. + """ + The updated user name the user wishes to change to. + """ userName: EmailAddress - # The updated preferred language the user wishes to change to. + """ + The updated preferred language the user wishes to change to. + """ preferredLang: LanguageEnums - # The method in which the user wishes to have their TFA code sent via. + """ + The method in which the user wishes to have their TFA code sent via. + """ tfaSendMethod: TFASendMethodEnum + + """ + The updated boolean which represents if the user wants to see features in progress. + """ + insideUser: Boolean + + """ + The updated boolean which represents if the user wants to receive update emails. + """ + receiveUpdateEmails: Boolean clientMutationId: String } type VerifyAccountPayload { - # \`VerifyAccountUnion\` returning either a \`VerifyAccountResult\`, or \`VerifyAccountError\` object. + """ + 'VerifyAccountUnion' returning either a 'VerifyAccountResult', or 'VerifyAccountError' object. + """ result: VerifyAccountUnion clientMutationId: String } - # This union is used with the \`verifyAccount\` mutation, allowing for users to verify their account, and support any errors that may occur + """ + This union is used with the 'verifyAccount' mutation, allowing for users to verify their account, and support any errors that may occur + """ union VerifyAccountUnion = VerifyAccountError | VerifyAccountResult - # This object is used to inform the user if any errors occurred while verifying their account. + """ + This object is used to inform the user if any errors occurred while verifying their account. + """ type VerifyAccountError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while verifying their account. + """ + This object is used to inform the user that no errors were encountered while verifying their account. + """ type VerifyAccountResult { - # Informs the user if their account was successfully verified. + """ + Informs the user if their account was successfully verified. + """ status: String } input VerifyAccountInput { - # Token sent via email, and located in url. + """ + Token sent via email, and located in url. + """ verifyTokenString: String! clientMutationId: String } type verifyPhoneNumberPayload { - # \`VerifyPhoneNumberUnion\` returning either a \`VerifyPhoneNumberResult\`, or \`VerifyPhoneNumberError\` object. + """ + 'VerifyPhoneNumberUnion' returning either a 'VerifyPhoneNumberResult', or 'VerifyPhoneNumberError' object. + """ result: VerifyPhoneNumberUnion clientMutationId: String } - # This union is used with the \`verifyPhoneNumber\` mutation, allowing for users to verify their phone number, and support any errors that may occur - union VerifyPhoneNumberUnion = - VerifyPhoneNumberError - | VerifyPhoneNumberResult + """ + This union is used with the 'verifyPhoneNumber' mutation, allowing for users to verify their phone number, and support any errors that may occur + """ + union VerifyPhoneNumberUnion = VerifyPhoneNumberError | VerifyPhoneNumberResult - # This object is used to inform the user if any errors occurred while verifying their phone number. + """ + This object is used to inform the user if any errors occurred while verifying their phone number. + """ type VerifyPhoneNumberError { - # Error code to inform user what the issue is related to. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue that was encountered. + """ + Description of the issue that was encountered. + """ description: String } - # This object is used to inform the user that no errors were encountered while verifying their phone number. + """ + This object is used to inform the user that no errors were encountered while verifying their phone number. + """ type VerifyPhoneNumberResult { - # Informs the user if their phone number was successfully verified. + """ + Informs the user if their phone number was successfully verified. + """ status: String - # The user who verified their phone number. + """ + The user who verified their phone number. + """ user: PersonalUser } input verifyPhoneNumberInput { - # The two factor code that was received via text message. + """ + The two factor code that was received via text message. + """ twoFactorCode: Int! clientMutationId: String } - - type Subscription { - # This subscription allows the user to receive dkim data directly from the scanners in real time. - dkimScanData: DkimSub - - # This subscription allows the user to receive dmarc data directly from the scanners in real time. - dmarcScanData: DmarcSub - - # This subscription allows the user to receive spf data directly from the scanners in real time. - spfScanData: SpfSub - - # This subscription allows the user to receive https data directly from the scanners in real time. - httpsScanData: HttpsSub - - # This subscription allows the user to receive ssl data directly from the scanners in real time. - sslScanData: SslSub - } - - # DKIM gql object containing the fields for the \`dkimScanData\` subscription. - type DkimSub { - # The shared id to match scans together. - sharedId: ID - - # The domain the scan was ran on. - domain: Domain - - # The success status of the scan. - status: StatusEnum - - # Individual scans results for each dkim selector. - results: [DkimResultSub] - } - - # Individual one-off scans results for the given dkim selector. - type DkimResultSub { - # The selector the scan was ran on. - selector: String - - # DKIM record retrieved during the scan of the domain. - record: String - - # Size of the Public Key in bits - keyLength: String - - # Raw scan result. - rawJson: JSON - - # Negative guidance tags found during scan. - negativeGuidanceTags: [GuidanceTag] - - # Neutral guidance tags found during scan. - neutralGuidanceTags: [GuidanceTag] - - # Positive guidance tags found during scan. - positiveGuidanceTags: [GuidanceTag] - } - - # DMARC gql object containing the fields for the \`dkimScanData\` subscription. - type DmarcSub { - # The shared id to match scans together. - sharedId: ID - - # The domain the scan was ran on. - domain: Domain - - # The current dmarc phase the domain is compliant to. - dmarcPhase: String - - # The success status of the scan. - status: StatusEnum - - # DMARC record retrieved during scan. - record: String - - # The requested policy you wish mailbox providers to apply - # when your email fails DMARC authentication and alignment checks. - pPolicy: String - - # This tag is used to indicate a requested policy for all - # subdomains where mail is failing the DMARC authentication and alignment checks. - spPolicy: String - - # The percentage of messages to which the DMARC policy is to be applied. - pct: Int - - # Raw scan result. - rawJson: JSON - - # Negative guidance tags found during DMARC Scan. - negativeGuidanceTags: [GuidanceTag] - - # Neutral guidance tags found during DMARC Scan. - neutralGuidanceTags: [GuidanceTag] - - # Positive guidance tags found during DMARC Scan. - positiveGuidanceTags: [GuidanceTag] - } - - # SPF gql object containing the fields for the \`dkimScanData\` subscription. - type SpfSub { - # The shared id to match scans together. - sharedId: ID - - # The domain the scan was ran on. - domain: Domain - - # The success status of the scan. - status: StatusEnum - - # The amount of DNS lookups. - lookups: Int - - # SPF record retrieved during the scan of the given domain. - record: String - - # Instruction of what a recipient should do if there is not a match to your SPF record. - spfDefault: String - - # Raw scan result. - rawJson: JSON - - # Negative guidance tags found during scan. - negativeGuidanceTags: [GuidanceTag] - - # Neutral guidance tags found during scan. - neutralGuidanceTags: [GuidanceTag] - - # Positive guidance tags found during scan. - positiveGuidanceTags: [GuidanceTag] - } - - # HTTPS gql object containing the fields for the \`dkimScanData\` subscription. - type HttpsSub { - # The shared id to match scans together. - sharedId: ID - - # The domain the scan was ran on. - domain: Domain - - # The success status of the scan. - status: StatusEnum - - # State of the HTTPS implementation on the server and any issues therein. - implementation: String - - # Degree to which HTTPS is enforced on the server based on behaviour. - enforced: String - - # Presence and completeness of HSTS implementation. - hsts: String - - # Denotes how long the domain should only be accessed using HTTPS - hstsAge: String - - # Denotes whether the domain has been submitted and included within HSTS preload list. - preloaded: String - - # Raw scan result. - rawJson: JSON - - # Negative guidance tags found during scan. - negativeGuidanceTags: [GuidanceTag] - - # Neutral guidance tags found during scan. - neutralGuidanceTags: [GuidanceTag] - - # Positive guidance tags found during scan. - positiveGuidanceTags: [GuidanceTag] - } - - # SSL gql object containing the fields for the \`dkimScanData\` subscription. - type SslSub { - # The shared id to match scans together. - sharedId: ID - - # The domain the scan was ran on. - domain: Domain - - # The success status of the scan. - status: StatusEnum - - # List of ciphers in use by the server deemed to be "acceptable". - acceptableCiphers: [String] - - # List of curves in use by the server deemed to be "acceptable". - acceptableCurves: [String] - - # Denotes vulnerability to OpenSSL CCS Injection. - ccsInjectionVulnerable: Boolean - - # Denotes vulnerability to "Heartbleed" exploit. - heartbleedVulnerable: Boolean - - # List of ciphers in use by the server deemed to be "strong". - strongCiphers: [String] - - # List of curves in use by the server deemed to be "strong". - strongCurves: [String] - - # Denotes support for elliptic curve key pairs. - supportsEcdhKeyExchange: Boolean - - # List of ciphers in use by the server deemed to be "weak" or in other words, are not compliant with security standards. - weakCiphers: [String] - - # List of curves in use by the server deemed to be "weak" or in other words, are not compliant with security standards. - weakCurves: [String] - - # Raw scan result. - rawJson: JSON - - # Negative guidance tags found during scan. - negativeGuidanceTags: [GuidanceTag] - - # Neutral guidance tags found during scan. - neutralGuidanceTags: [GuidanceTag] - - # Positive guidance tags found during scan. - positiveGuidanceTags: [GuidanceTag] - } ` diff --git a/frontend/mocking/mocker.js b/frontend/mocking/mocker.js index 1a0d4f050b..b201de249d 100644 --- a/frontend/mocking/mocker.js +++ b/frontend/mocking/mocker.js @@ -1,9 +1,10 @@ import { makeExecutableSchema } from '@graphql-tools/schema' import { addMocksToSchema } from '@graphql-tools/mock' import { getTypeNames } from './faked_schema' -import { ApolloServer, PubSub } from 'apollo-server' +import { ApolloServer } from 'apollo-server' +import { PubSub } from 'graphql-subscriptions' import faker from 'faker' -import { connectionFromArray } from 'graphql-relay/lib/connection/arrayconnection' +import { connectionFromArray } from 'graphql-relay' import { getStringOfDomains } from './helpers/getStringOfDomains' import { getDmarcTableResults } from './helpers/getDmarcTableResults' import { getDkimSelectors } from './helpers/getDkimSelectors' @@ -26,24 +27,13 @@ const parseCookies = (str) => { }, {}) } -const now = () => Math.floor(new Date().getTime() / 1000) - -const future = (expPeriod) => - Math.floor(new Date((now() + expPeriod) * 1000) / 1000) - -const tokenize = ({ - parameters = {}, - expPeriod = JWT_TOKEN_EXPIRY_SECONDS, // seconds until expiry - iat = now(), - exp = future(expPeriod), -}) => +const tokenize = ({ expiresIn = '15m', parameters = {} }) => jwt.sign( { - exp, - iat, parameters, }, 'secret', + { expiresIn: expiresIn }, ) const pubsub = new PubSub() @@ -65,7 +55,6 @@ const mockOverrides = { totalCount: affiliationCount, }, emailValidated: true, - preferredLang: 'ENGLISH', } }, SignInUnion: () => ({ __typename: 'AuthResult' }), @@ -143,11 +132,21 @@ const mocks = { totalMessages: faker.datatype.number({ min: 0, max: 15000 }), } }, + ConnectionInfo: () => { + return { + headers: { + 'Content-Length': '62', + 'Content-Type': 'text/html; charset=utf-8', + }, + } + }, Date: () => { // gives date in format "2020-12-31 15:30:20.262Z" - return new Date(faker.date.between('2019-01-01', '2022-01-01')) - .toISOString() - .replace('T', ' ') + return new Date(faker.date.between('2019-01-01', '2022-01-01')).toISOString().replace('T', ' ') + }, + DateTime: () => { + // gives date in format "2020-12-31 15:30:20.262Z" + return new Date(faker.date.between('2019-01-01', '2022-01-01')).toISOString() }, DkimFailureTable: () => { const dkimDomains = getStringOfDomains(0, 2) @@ -213,11 +212,7 @@ const mocks = { }, DmarcSub: () => { return { - dmarcPhase: faker.helpers.randomize([ - 'maintain', - 'deploy', - 'not implemented', - ]), + dmarcPhase: faker.helpers.randomize(['maintain', 'deploy', 'not implemented']), } }, DmarcSummaryConnection: () => { @@ -231,18 +226,10 @@ const mocks = { // gives date in format "2020-12-31 15:30:20.262Z" const lastRan = Math.random() > 0.2 - ? new Date(faker.date.between('2019-01-01', '2022-01-01')) - .toISOString() - .replace('T', ' ') + ? new Date(faker.date.between('2019-01-01', '2022-01-01')).toISOString().replace('T', ' ') : null const curDate = new Date() - const dmarcPhase = faker.random.arrayElement([ - 'assess', - 'deploy', - 'enforce', - 'maintain', - 'not implemented', - ]) + const dmarcPhase = faker.random.arrayElement(['assess', 'deploy', 'enforce', 'maintain', 'not implemented']) // generate an object matching DmarcSummary const generateFakeSummary = (currentDate, month, year) => { @@ -264,23 +251,13 @@ const mocks = { }) const failCount = maxNumber - passDkimOnlyCount - const fullPassPercent = Math.round( - (100 * fullPassCount) / totalMessageCount, - ) - const passSpfOnlyPercent = Math.round( - (100 * passSpfOnlyCount) / totalMessageCount, - ) - const passDkimOnlyPercent = Math.round( - (100 * passDkimOnlyCount) / totalMessageCount, - ) + const fullPassPercent = Math.round((100 * fullPassCount) / totalMessageCount) + const passSpfOnlyPercent = Math.round((100 * passSpfOnlyCount) / totalMessageCount) + const passDkimOnlyPercent = Math.round((100 * passDkimOnlyCount) / totalMessageCount) const failPercent = Math.round((100 * failCount) / totalMessageCount) return { - month: - month || - currentDate - .toLocaleString('default', { month: 'long' }) - .toUpperCase(), + month: month || currentDate.toLocaleString('default', { month: 'long' }).toUpperCase(), year: year || currentDate.getFullYear(), categoryTotals: { fullPass: fullPassCount, @@ -352,20 +329,13 @@ const mocks = { GuidanceTag: () => { const tagId = 'tag' + faker.datatype.number({ min: 1, max: 14 }) const tagName = - 'TAG-' + - faker.helpers.randomize([ - 'missing', - 'downgraded', - 'bad-chain', - 'short-age', - 'certificate-expired', - ]) - const guidance = faker.lorem.sentence() + 'TAG-' + faker.helpers.randomize(['missing', 'downgraded', 'bad-chain', 'short-age', 'certificate-expired']) + // const guidance = faker.lorem.sentence() const refLinks = [...new Array(1)] const refLinksTech = [...new Array(1)] return { - guidance, + // guidance, refLinks, refLinksTech, tagId, @@ -380,6 +350,58 @@ const mocks = { totalCount: numberOfEdges, } }, + MyTrackerResult: () => { + const domainCount = faker.datatype.number({ min: 0, max: 500 }) + const httpsPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const httpsFailCount = domainCount - httpsPassCount + const httpsPassPercentage = (httpsPassCount / domainCount) * 100 + const httpsFailPercentage = 100 - httpsPassPercentage + const https = { + total: domainCount, + categories: [ + { + name: 'pass', + count: httpsPassCount, + percentage: httpsPassPercentage, + }, + { + name: 'fail', + count: httpsFailCount, + percentage: httpsFailPercentage, + }, + ], + } + + const mailPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const mailFailCount = domainCount - mailPassCount + const mailPassPercentage = (mailPassCount / domainCount) * 100 + const mailFailPercentage = 100 - mailPassPercentage + const dmarc = { + total: domainCount, + categories: [ + { + name: 'pass', + count: mailPassCount, + percentage: mailPassPercentage, + }, + { + name: 'fail', + count: mailFailCount, + percentage: mailFailPercentage, + }, + ], + } + + const dmarcPhase = dmarcPhaseSummaryMock() + return { + domainCount, + domains: { + edges: [...new Array(domainCount)], + totalCount: domainCount, + }, + summaries: { https, dmarc, dmarcPhase }, + } + }, Organization: () => { const name = faker.company.companyName() const slug = faker.helpers.slugify(name) @@ -388,22 +410,22 @@ const mocks = { const city = location.city const province = location.province - const webPassCount = faker.datatype.number({ min: 0, max: domainCount }) - const webFailCount = domainCount - webPassCount - const webPassPercentage = (webPassCount / domainCount) * 100 - const webFailPercentage = 100 - webPassPercentage - const web = { + const httpsPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const httpsFailCount = domainCount - httpsPassCount + const httpsPassPercentage = (httpsPassCount / domainCount) * 100 + const httpsFailPercentage = 100 - httpsPassPercentage + const https = { total: domainCount, categories: [ { name: 'pass', - count: webPassCount, - percentage: webPassPercentage, + count: httpsPassCount, + percentage: httpsPassPercentage, }, { name: 'fail', - count: webFailCount, - percentage: webFailPercentage, + count: httpsFailCount, + percentage: httpsFailPercentage, }, ], } @@ -428,6 +450,8 @@ const mocks = { ], } + const dmarcPhase = dmarcPhaseSummaryMock() + const affiliationCount = faker.datatype.number({ min: 0, max: 200 }) return { @@ -444,7 +468,7 @@ const mocks = { name, province, slug, - summaries: { web, mail }, + summaries: { https, mail, dmarcPhase }, } }, OrganizationConnection: () => { @@ -454,6 +478,13 @@ const mocks = { totalCount: numberOfEdges, } }, + OrganizationSummaryConnection: () => { + const numberOfEdges = faker.datatype.number({ min: 30, max: 365 }) + return { + edges: [...new Array(numberOfEdges)], + totalCount: numberOfEdges, + } + }, PersonalUser: () => { const affiliationCount = faker.datatype.number({ min: 0, max: 200 }) @@ -475,8 +506,7 @@ const mocks = { refLink, } }, - Selector: () => - 'selector' + faker.datatype.number({ min: 1, max: 9 }) + '._domainkey', + Selector: () => 'selector' + faker.datatype.number({ min: 1, max: 9 }), SignInError: () => ({ description: 'Mocked sign in error description', }), @@ -527,20 +557,18 @@ const mocks = { min: 2019, max: 2021, }), + MXHost: () => ({ + addresses: [...new Array(faker.datatype.number({ min: 1, max: 2 }))].map(() => faker.internet.ip()), + hostname: faker.internet.domainName(), + preference: faker.datatype.number({ min: 1, max: 10 }), + }), ...mockOverrides, } -const getConnectionObject = (store, args, resolveInfo) => { - // use key of calling object to ensure consistency - const allEdges = store.get( - resolveInfo.returnType.toString(), - resolveInfo.path.key, - 'edges', - ) - +const edgesToConnection = (store, edges, args) => { // we only need the nodes since connectionFromArray will generate the proper cursors // extract all nodes and place into new array - const allNodes = allEdges.map((edge) => { + const allNodes = edges.map((edge) => { return store.get(edge.$ref.typeName, edge.$ref.key, 'node') }) @@ -552,6 +580,63 @@ const getConnectionObject = (store, args, resolveInfo) => { } } +const getConnectionObject = (store, args, resolveInfo) => { + // use key of calling object to ensure consistency + const allEdges = store.get(resolveInfo.returnType.toString(), resolveInfo.path.key, 'edges') + + return edgesToConnection(store, allEdges, args) +} + +const dmarcPhaseSummaryMock = () => { + // DMARC phases: + // NA. Not Implemented + // 1. Assess + // 2. Deploy + // 3. Enforce + // 4. Maintain + + const notImplementedTotal = faker.datatype.number({ min: 1, max: 2000 }) + const assessTotal = faker.datatype.number({ min: 1, max: 2000 }) + const deployTotal = faker.datatype.number({ min: 1, max: 2000 }) + const enforceTotal = faker.datatype.number({ min: 1, max: 2000 }) + const maintainTotal = faker.datatype.number({ min: 1, max: 2000 }) + + const totalDomains = notImplementedTotal + assessTotal + deployTotal + enforceTotal + maintainTotal + + const notImplementedCategory = { + name: 'not implemented', + count: notImplementedTotal, + percentage: (notImplementedTotal / totalDomains) * 100, + } + const assessCategory = { + name: 'assess', + count: assessTotal, + percentage: (assessTotal / totalDomains) * 100, + } + const deployCategory = { + name: 'deploy', + count: deployTotal, + percentage: (deployTotal / totalDomains) * 100, + } + const enforceCategory = { + name: 'enforce', + count: enforceTotal, + percentage: (enforceTotal / totalDomains) * 100, + } + const maintainCategory = { + name: 'maintain', + count: maintainTotal, + percentage: (maintainTotal / totalDomains) * 100, + } + + const categories = [notImplementedCategory, assessCategory, deployCategory, enforceCategory, maintainCategory] + + return { + total: totalDomains, + categories, + } +} + // Create a new schema with mocks and resolvers const schemaWithMocks = addMocksToSchema({ schema, @@ -559,10 +644,7 @@ const schemaWithMocks = addMocksToSchema({ resolvers: (store) => ({ Query: { findMe: (_, _args, context, _resolveInfo, ___) => { - return store.get( - 'PersonalUser', - jwt.decode(context.token, 'secret').parameters.userKey, - ) + return store.get('PersonalUser', jwt.decode(context.token, 'secret').parameters.userKey) }, findMyDmarcSummaries: (_, args, _context, resolveInfo, ___) => { return getConnectionObject(store, args, resolveInfo) @@ -570,9 +652,18 @@ const schemaWithMocks = addMocksToSchema({ findMyDomains: (_, args, _context, resolveInfo) => { return getConnectionObject(store, args, resolveInfo) }, + findOrganizationBySlug: (_, args, _context, _resolveInfo) => { + return store.get('Organization', args.orgSlug) + }, findMyOrganizations: (_, args, _context, resolveInfo) => { return getConnectionObject(store, args, resolveInfo) }, + findMyTracker: (_, _args, _context, _resolveInfo) => { + return store.get('MyTrackerResult') + }, + dmarcPhaseSummary: (_, _args, _context, _resolveInfo) => { + return dmarcPhaseSummaryMock() + }, }, DetailTables: { dkimFailure: (_, args, _context, resolveInfo) => { @@ -588,12 +679,14 @@ const schemaWithMocks = addMocksToSchema({ return getConnectionObject(store, args, resolveInfo) }, }, - WebScan: { - https: (_, args, _context, resolveInfo) => { - return getConnectionObject(store, args, resolveInfo) + Organization: { + affiliations: (parent, args, _context, _resolveInfo) => { + const organizationAffiliationEdges = store.get(parent, ['affiliations', 'edges']) + return edgesToConnection(store, organizationAffiliationEdges, args) }, - ssl: (_, args, _context, resolveInfo) => { - return getConnectionObject(store, args, resolveInfo) + domains: (parent, args, _context, _resolveInfo) => { + const organizationDomainEdges = store.get(parent, ['domains', 'edges']) + return edgesToConnection(store, organizationDomainEdges, args) }, }, Mutation: { @@ -617,10 +710,9 @@ const schemaWithMocks = addMocksToSchema({ const refreshToken = tokenize({ parameters: { - userKey: jwt.decode(context.cookies.refresh_token, 'secret') - .parameters.userKey, + userKey: jwt.decode(context.cookies.refresh_token, 'secret').parameters.userKey, }, - expPeriod: REFRESH_TOKEN_EXPIRY_SECONDS, + expiresIn: '15m', }) context.res.cookie('refresh_token', refreshToken, cookieData) @@ -628,15 +720,10 @@ const schemaWithMocks = addMocksToSchema({ result: { authToken: tokenize({ parameters: { - userKey: jwt.decode(context.cookies.refresh_token, 'secret') - .parameters.userKey, + userKey: jwt.decode(context.cookies.refresh_token, 'secret').parameters.userKey, }, }), - user: store.get( - 'PersonalUser', - jwt.decode(context.cookies.refresh_token, 'secret').parameters - .userKey, - ), + user: store.get('PersonalUser', jwt.decode(context.cookies.refresh_token, 'secret').parameters.userKey), type: 'AuthResult', }, } @@ -647,66 +734,41 @@ const schemaWithMocks = addMocksToSchema({ store.set('DkimSub', scanUuid, 'sharedId', scanUuid) const dkimScanData = store.get('DkimSub', scanUuid) setTimeout(() => { - pubsub.publish( - `${NEW_DKIM_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - { - dkimScanData: dkimScanData, - }, - ) + pubsub.publish(`${NEW_DKIM_DATA_STREAM}/${jwt.decode(context.token, 'secret').parameters.userKey}`, { + dkimScanData: dkimScanData, + }) }, Math.random() * 10000) store.set('DmarcSub', scanUuid, 'sharedId', scanUuid) const dmarcScanData = store.get('DmarcSub', scanUuid) setTimeout(() => { - pubsub.publish( - `${NEW_DMARC_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - { - dmarcScanData: dmarcScanData, - }, - ) + pubsub.publish(`${NEW_DMARC_DATA_STREAM}/${jwt.decode(context.token, 'secret').parameters.userKey}`, { + dmarcScanData: dmarcScanData, + }) }, Math.random() * 10000) store.set('SpfSub', scanUuid, 'sharedId', scanUuid) const spfScanData = store.get('SpfSub', scanUuid) setTimeout(() => { - pubsub.publish( - `${NEW_SPF_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - { - spfScanData: spfScanData, - }, - ) + pubsub.publish(`${NEW_SPF_DATA_STREAM}/${jwt.decode(context.token, 'secret').parameters.userKey}`, { + spfScanData: spfScanData, + }) }, Math.random() * 10000) store.set('HttpsSub', scanUuid, 'sharedId', scanUuid) const httpsScanData = store.get('HttpsSub', scanUuid) setTimeout(() => { - pubsub.publish( - `${NEW_HTTPS_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - { - httpsScanData: httpsScanData, - }, - ) + pubsub.publish(`${NEW_HTTPS_DATA_STREAM}/${jwt.decode(context.token, 'secret').parameters.userKey}`, { + httpsScanData: httpsScanData, + }) }, Math.random() * 10000) store.set('SslSub', scanUuid, 'sharedId', scanUuid) const sslScanData = store.get('SslSub', scanUuid) setTimeout(() => { - pubsub.publish( - `${NEW_SSL_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - { - sslScanData: sslScanData, - }, - ) + pubsub.publish(`${NEW_SSL_DATA_STREAM}/${jwt.decode(context.token, 'secret').parameters.userKey}`, { + sslScanData: sslScanData, + }) }, Math.random() * 10000) return { @@ -727,12 +789,7 @@ const schemaWithMocks = addMocksToSchema({ if (key === 'id') return // Current mock implementation does not support multi-lang, remove language from keys - store.set( - 'Organization', - args.input.id, - key.substring(0, key.length - 2), - value, - ) + store.set('Organization', args.input.id, key.substring(0, key.length - 2), value) }) return { @@ -748,12 +805,8 @@ const schemaWithMocks = addMocksToSchema({ ) return { result: { - status: - 'Phone number has been successfully set, you will receive a verification text message shortly.', - user: store.get( - 'PersonalUser', - jwt.decode(context.token, 'secret').parameters.userKey, - ), + status: 'Phone number has been successfully set, you will receive a verification text message shortly.', + user: store.get('PersonalUser', jwt.decode(context.token, 'secret').parameters.userKey), type: 'SetPhoneNumberResult', }, } @@ -772,7 +825,6 @@ const schemaWithMocks = addMocksToSchema({ const refreshToken = tokenize({ parameters: { userKey: userId, - expPeriod: REFRESH_TOKEN_EXPIRY_SECONDS, }, }) @@ -805,48 +857,6 @@ const schemaWithMocks = addMocksToSchema({ if (obj.authToken) return 'AuthResult' }, }, - Subscription: { - dkimScanData: { - subscribe: (_, _args, context) => - pubsub.asyncIterator( - `${NEW_DKIM_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - ), - }, - dmarcScanData: { - subscribe: (_, _args, context) => - pubsub.asyncIterator( - `${NEW_DMARC_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - ), - }, - spfScanData: { - subscribe: (_, _args, context) => - pubsub.asyncIterator( - `${NEW_SPF_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - ), - }, - httpsScanData: { - subscribe: (_, _args, context) => - pubsub.asyncIterator( - `${NEW_HTTPS_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - ), - }, - sslScanData: { - subscribe: (_, _args, context) => - pubsub.asyncIterator( - `${NEW_SSL_DATA_STREAM}/${ - jwt.decode(context.token, 'secret').parameters.userKey - }`, - ), - }, - }, }), }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 46c7894c7c..11774ffd9c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,139 +9,173 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@apollo/client": "^3.3.13", - "@babel/runtime": "^7.12.1", - "@chakra-ui/icons": "^1.0.14", - "@chakra-ui/react": "^1.6.5", - "@emotion/react": "^11.4.0", - "@emotion/styled": "^11.3.0", - "@lingui/react": "^3.10.2", - "@visx/axis": "^1.17.0", - "@visx/event": "^1.7.0", - "@visx/grid": "^1.16.0", - "@visx/group": "^1.7.0", - "@visx/legend": "^1.14.0", - "@visx/mock-data": "^1.7.0", - "@visx/responsive": "^1.10.1", - "@visx/scale": "^1.14.0", - "@visx/shape": "^1.16.0", - "@visx/tooltip": "^1.7.2", - "body-parser": "^1.19.0", - "d3": "^6.2.0", - "d3-scale": "^3.2.3", - "d3-selection": "^2.0.0", - "dotenv": "^8.2.0", - "express": "^4.17.1", - "fast-deep-equal": "^3.1.3", - "formik": "^2.2.0", + "@apollo/client": "^3.6.9", + "@babel/runtime": "^7.27.0", + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@chakra-ui/system": "^2.6.2", + "@emotion/react": "^11.9.3", + "@emotion/styled": "^11.9.3", + "@lingui/react": "^5.9.3", + "@visx/axis": "^2.17.0", + "@visx/curve": "^3.3.0", + "@visx/event": "^2.17.0", + "@visx/glyph": "^3.3.0", + "@visx/gradient": "^3.3.0", + "@visx/grid": "^3.5.0", + "@visx/group": "^2.1.0", + "@visx/legend": "^2.17.0", + "@visx/mock-data": "^3.3.0", + "@visx/responsive": "^2.17.0", + "@visx/scale": "^3.5.0", + "@visx/shape": "^3.5.0", + "@visx/tooltip": "^2.17.0", + "@visx/vendor": "^3.5.0", + "body-parser": "^1.20.4", + "compression": "^1.8.1", + "d3": "^7.8.0", + "dotenv": "^16.0.1", + "express": "^4.22.0", + "formik": "^2.2.9", "framer-motion": "^4.1.17", - "graphql-tag": "^2.11.0", + "intro.js": "^7.2.0", + "intro.js-react": "^1.0.0", "isomorphic-unfetch": "^3.1.0", - "make-plural": "^6.2.2", - "prop-types": "^15.7.2", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-error-boundary": "^3.1.3", - "react-phone-input-2": "^2.14.0", - "react-router-dom": "^5.2.0", - "react-select": "^4.3.1", - "react-swipe": "^6.0.4", - "react-table": "^7.6.0", - "subscriptions-transport-ws": "^0.9.19", - "yup": "^0.29.3" + "json-2-csv": "^3.17.1", + "lodash-es": "^4.18.1", + "make-plural": "^7.1.0", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.4", + "react-joyride": "^2.8.2", + "react-phone-input-2": "^2.15.1", + "react-router-dom": "^6.30.3", + "react-table": "^7.8.0", + "yup": "^0.32.11" }, "devDependencies": { - "@babel/cli": "^7.13.16", - "@babel/core": "^7.12.10", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/preset-env": "^7.12.11", - "@babel/preset-react": "^7.13.13", - "@graphql-tools/mock": "^8.1.3", - "@graphql-tools/schema": "^7.1.5", - "@hot-loader/react-dom": "^16.14.0", - "@lingui/cli": "^3.10.2", - "@lingui/loader": "^3.10.2", - "@lingui/macro": "^3.10.2", - "@testing-library/jest-dom": "^5.11.5", - "@testing-library/react": "^11.1.1", - "@testing-library/react-hooks": "^5.0.0", - "@testing-library/user-event": "^13.1.9", - "acorn": "^8.1.0", - "apollo-server": "^2.24.0", - "babel-loader": "^8.2.2", - "babel-plugin-macros": "^3.0.1", + "@babel/cli": "^7.18.6", + "@babel/core": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-transform-runtime": "^7.18.6", + "@babel/preset-env": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@graphql-tools/mock": "^8.7.0", + "@graphql-tools/schema": "^10.0.0", + "@lingui/cli": "^5.9.3", + "@lingui/core": "^5.9.3", + "@lingui/loader": "^5.9.3", + "@lingui/macro": "^5.9.3", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.4.0", + "@testing-library/react-hooks": "^7.0.2", + "@testing-library/user-event": "^13.5.0", + "acorn": "^8.7.0", + "apollo-server": "^3.12.0", + "babel-loader": "^8.2.5", + "babel-plugin-macros": "^3.1.0", "clean-webpack-plugin": "^3.0.0", - "eslint": "^7.11.0", - "eslint-config-prettier": "^8.1.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.3.1", - "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-standard": "^4.0.1", - "faker": "^5.5.3", - "graphql": "^15.5.0", - "graphql-relay": "^0.6.0", - "html-webpack-plugin": "^5.0.0-beta.6", - "jest": "^26.6.2", - "jest-emotion": "^10.0.32", - "jsonwebtoken": "^8.5.1", + "copy-webpack-plugin": "^14.0.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "faker": "5.5.3", + "graphql": "^16.8.1", + "graphql-relay": "^0.10.0", + "graphql-subscriptions": "^2.0.0", + "html-webpack-plugin": "^5.5.0", + "jest": "^26.6.3", + "jest-emotion": "^11.0.0", + "jsonwebtoken": "^9.0.0", "mq-polyfill": "^1.1.8", - "nodemon": "^2.0.7", - "prettier": "^2.1.2", + "nodemon": "^2.0.19", + "prettier": "^2.7.1", "react-hot-loader": "^4.13.0", - "react-test-renderer": "^16.14.0", - "source-map-loader": "^2.0.1", - "supertest": "^6.1.3", - "webpack": "^5.0.2", - "webpack-cli": "^4.6.0", + "react-test-renderer": "^18.2.0", + "source-map-loader": "^4.0.0", + "supertest": "^6.2.3", + "webpack": "^5.105.0", + "webpack-cli": "^4.10.0", "webpack-config-utils": "^2.3.1", - "webpack-dev-server": "^3.11.0" + "webpack-dev-server": "^4.9.2" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", + "dev": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "devOptional": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@apollo/client": { - "version": "3.3.13", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.3.13.tgz", - "integrity": "sha512-usiVmXiOq0J/GpyIOIhlF8ItHpiPJObC7zoxLYPoOYj3G3OB0hCIcUKs3aTJ3ATW7u8QxvYgRaJg72NN7E1WOg==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.6.9.tgz", + "integrity": "sha512-Y1yu8qa2YeaCUBVuw08x8NHenFi0sw2I3KCu7Kw9mDSu86HmmtHJkCAifKVrN2iPgDTW/BbP3EpSV8/EQCcxZA==", "dependencies": { - "@graphql-typed-document-node/core": "^3.0.0", - "@types/zen-observable": "^0.8.0", + "@graphql-typed-document-node/core": "^3.1.1", "@wry/context": "^0.6.0", - "@wry/equality": "^0.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graphql-tag": "^2.12.0", + "@wry/equality": "^0.5.0", + "@wry/trie": "^0.3.0", + "graphql-tag": "^2.12.6", "hoist-non-react-statics": "^3.3.2", - "optimism": "^0.15.0", + "optimism": "^0.16.1", "prop-types": "^15.7.2", - "symbol-observable": "^2.0.0", - "ts-invariant": "^0.7.0", - "tslib": "^1.10.0", - "zen-observable": "^0.8.14" + "symbol-observable": "^4.0.0", + "ts-invariant": "^0.10.3", + "tslib": "^2.3.0", + "zen-observable-ts": "^1.2.5" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", + "graphql-ws": "^5.5.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + }, + "react": { + "optional": true + }, + "subscriptions-transport-ws": { + "optional": true + } } }, - "node_modules/@apollo/client/node_modules/graphql-tag": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.3.tgz", - "integrity": "sha512-5wJMjSvj30yzdciEuk9dPuUBUR56AqDi3xncoYQl1i42pGdSqOJrJsdb/rz5BDoy+qoGvQwABcBeF0xXY3TrKw==", + "node_modules/@apollo/client/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/@apollo/client/node_modules/zen-observable-ts": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz", + "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==", "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=10" + "zen-observable": "0.8.15" } }, - "node_modules/@apollo/client/node_modules/graphql-tag/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, "node_modules/@apollo/protobufjs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", - "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", + "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -156,7 +190,6 @@ "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/long": "^4.0.0", - "@types/node": "^10.1.0", "long": "^4.0.0" }, "bin": { @@ -164,118 +197,168 @@ "apollo-pbts": "bin/pbts" } }, - "node_modules/@apollo/protobufjs/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true + "node_modules/@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "dev": true, + "dependencies": { + "@apollo/protobufjs": "1.2.7" + } }, - "node_modules/@apollographql/apollo-tools": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.1.tgz", - "integrity": "sha512-ZII+/xUFfb9ezDU2gad114+zScxVFMVlZ91f8fGApMzlS1kkqoyLnC4AJaQ1Ya/X+b63I20B4Gd+eCL8QuB4sA==", + "node_modules/@apollo/utils.dropunuseddefinitions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-1.1.0.tgz", + "integrity": "sha512-jU1XjMr6ec9pPoL+BFWzEPW7VHHulVdGKMkPAMiCigpVIT11VmCbnij0bWob8uS3ODJ65tZLYKAh/55vLw2rbg==", "dev": true, "engines": { - "node": ">=8", - "npm": ">=6" + "node": ">=12.13.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollographql/graphql-playground-html": { - "version": "1.6.27", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.27.tgz", - "integrity": "sha512-tea2LweZvn6y6xFV11K0KC8ETjmm52mQrW+ezgB2O/aTQf8JGyFmMcRPFgUaQZeHbWdm8iisDC6EjOKsXu0nfw==", + "node_modules/@apollo/utils.keyvaluecache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz", + "integrity": "sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg==", "dev": true, "dependencies": { - "xss": "^1.0.8" + "@apollo/utils.logger": "^1.0.0", + "lru-cache": "7.10.1 - 7.13.1" } }, - "node_modules/@apollographql/graphql-upload-8-fork": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-upload-8-fork/-/graphql-upload-8-fork-8.1.3.tgz", - "integrity": "sha512-ssOPUT7euLqDXcdVv3Qs4LoL4BPtfermW1IOouaqEmj36TpHYDmYDIbKoSQxikd9vtMumFnP87OybH7sC9fJ6g==", + "node_modules/@apollo/utils.keyvaluecache/node_modules/lru-cache": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz", + "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==", "dev": true, - "dependencies": { - "@types/express": "*", - "@types/fs-capacitor": "*", - "@types/koa": "*", - "busboy": "^0.3.1", - "fs-capacitor": "^2.0.4", - "http-errors": "^1.7.3", - "object-path": "^0.11.4" + "engines": { + "node": ">=12" + } + }, + "node_modules/@apollo/utils.logger": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-1.0.1.tgz", + "integrity": "sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA==", + "dev": true + }, + "node_modules/@apollo/utils.printwithreducedwhitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-1.1.0.tgz", + "integrity": "sha512-GfFSkAv3n1toDZ4V6u2d7L4xMwLA+lv+6hqXicMN9KELSJ9yy9RzuEXaX73c/Ry+GzRsBy/fdSUGayGqdHfT2Q==", + "dev": true, + "engines": { + "node": ">=12.13.0" }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.removealiases": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-1.0.0.tgz", + "integrity": "sha512-6cM8sEOJW2LaGjL/0vHV0GtRaSekrPQR4DiywaApQlL9EdROASZU5PsQibe2MWeZCOhNrPRuHh4wDMwPsWTn8A==", + "dev": true, "engines": { - "node": ">=8.5" + "node": ">=12.13.0" }, "peerDependencies": { - "graphql": "0.13.1 - 15" + "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollographql/graphql-upload-8-fork/node_modules/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "node_modules/@apollo/utils.sortast": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-1.1.0.tgz", + "integrity": "sha512-VPlTsmUnOwzPK5yGZENN069y6uUHgeiSlpEhRnLFYwYNoJHsuJq2vXVwIaSmts015WTPa2fpz1inkLYByeuRQA==", "dev": true, "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "lodash.sortby": "^4.7.0" }, "engines": { - "node": ">= 0.6" + "node": ">=12.13.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@apollographql/graphql-upload-8-fork/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/@apollographql/graphql-upload-8-fork/node_modules/setprototypeof": { + "node_modules/@apollo/utils.stripsensitiveliterals": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-1.2.0.tgz", + "integrity": "sha512-E41rDUzkz/cdikM5147d8nfCFVKovXxKBcjvLEQ7bjZm/cg9zEcXvS6vFY8ugTubI3fn6zoqo0CyU8zT+BGP9w==", + "dev": true, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } }, - "node_modules/@ardatan/aggregate-error": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz", - "integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==", + "node_modules/@apollo/utils.usagereporting": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-1.0.1.tgz", + "integrity": "sha512-6dk+0hZlnDbahDBB2mP/PZ5ybrtCJdLMbeNJD+TJpKyZmSY6bA3SjI8Cr2EM9QA+AdziywuWg+SgbWUF3/zQqQ==", "dev": true, "dependencies": { - "tslib": "~2.0.1" + "@apollo/usage-reporting-protobuf": "^4.0.0", + "@apollo/utils.dropunuseddefinitions": "^1.1.0", + "@apollo/utils.printwithreducedwhitespace": "^1.1.0", + "@apollo/utils.removealiases": "1.0.0", + "@apollo/utils.sortast": "^1.1.0", + "@apollo/utils.stripsensitiveliterals": "^1.2.0" }, "engines": { - "node": ">=8" + "node": ">=12.13.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" } }, - "node_modules/@ardatan/aggregate-error/node_modules/tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", - "dev": true + "node_modules/@apollographql/apollo-tools": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz", + "integrity": "sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw==", + "dev": true, + "engines": { + "node": ">=8", + "npm": ">=6" + }, + "peerDependencies": { + "graphql": "^14.2.1 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@apollographql/graphql-playground-html": { + "version": "1.6.29", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", + "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", + "dev": true, + "dependencies": { + "xss": "^1.0.8" + } }, "node_modules/@babel/cli": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.16.tgz", - "integrity": "sha512-cL9tllhqvsQ6r1+d9Invf7nNXg/3BlfL1vvvL/AdH9fZ2l5j0CeBcoq6UjsqHpvyN1v5nXSZgqJZoGeK+ZOAbw==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.18.10.tgz", + "integrity": "sha512-dLvWH+ZDFAkd2jPBSghrsFBuXrREvFwjpDycXbmUoeochqKYe4zNSLEJYErpLg8dvxvZYe79/MkN461XCwpnGw==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.8", "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", + "glob": "^7.2.0", "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" + "slash": "^2.0.0" }, "bin": { "babel": "bin/babel.js", "babel-external-helpers": "bin/babel-external-helpers.js" }, + "engines": { + "node": ">=6.9.0" + }, "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", "chokidar": "^3.4.0" }, "peerDependencies": { @@ -314,38 +397,48 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dependencies": { - "@babel/highlight": "^7.12.13" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==" + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "devOptional": true, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/core": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz", - "integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0", - "convert-source-map": "^1.7.0", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "devOptional": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -355,301 +448,477 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "devOptional": true + }, "node_modules/@babel/core/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "devOptional": true, "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@babel/core/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "devOptional": true }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz", - "integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dependencies": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@babel/types": "^7.14.1", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", "dev": true, "dependencies": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", "dev": true, "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "devOptional": true, "dependencies": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "devOptional": true, + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, "bin": { "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "devOptional": true + }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz", - "integrity": "sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", + "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", + "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" } }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", - "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "@babel/types": "^7.12.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, "dependencies": { - "@babel/types": "^7.12.13" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, "dependencies": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dependencies": { - "@babel/types": "^7.13.12" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz", - "integrity": "sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "devOptional": true, "dependencies": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "@babel/types": "^7.18.6" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", "dev": true, - "dependencies": { - "lodash": "^4.17.19" + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", - "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/types": "^7.12.1" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", + "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", + "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "devOptional": true, "dependencies": { - "@babel/types": "^7.13.12" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", "dev": true, "dependencies": { - "@babel/types": "^7.12.1" - } + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, "dependencies": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "devOptional": true, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", - "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "devOptional": true, "dependencies": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz", - "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dependencies": { + "@babel/types": "^7.28.5" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -657,140 +926,285 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" } }, "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", - "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", - "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", - "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", - "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", - "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", - "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", - "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", - "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", - "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-async-generators": { @@ -812,12 +1226,30 @@ } }, "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-dynamic-import": { @@ -827,6 +1259,9 @@ "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-export-namespace-from": { @@ -836,6 +1271,24 @@ "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-import-meta": { @@ -857,11 +1310,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -924,468 +1378,740 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", - "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", - "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", - "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", - "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", - "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", + "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", - "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", - "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", + "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", - "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", - "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", - "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", - "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", - "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", - "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", - "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", - "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", - "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", - "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", + "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-identifier": "^7.18.6", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", - "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", - "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", + "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", - "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", - "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", - "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz", - "integrity": "sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz", - "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.10.tgz", + "integrity": "sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/types": "^7.13.12" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz", - "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==", - "dev": true, - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.12.17" - } - }, - "node_modules/@babel/plugin-transform-react-jsx/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", "dev": true, "dependencies": { - "@babel/types": "^7.12.13" + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", - "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", "dev": true, "dependencies": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", - "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "resolve": "^1.8.1", - "semver": "^5.5.1" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", - "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", - "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", + "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", - "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", - "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", - "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", - "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", - "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/preset-env": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", - "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.12.7", - "@babel/helper-compilation-targets": "^7.12.5", - "@babel/helper-module-imports": "^7.12.5", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.1", - "@babel/plugin-proposal-json-strings": "^7.12.1", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.7", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.7", - "@babel/plugin-proposal-private-methods": "^7.12.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@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.0", + "@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.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.1", - "@babel/plugin-transform-arrow-functions": "^7.12.1", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.11", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-computed-properties": "^7.12.1", - "@babel/plugin-transform-destructuring": "^7.12.1", - "@babel/plugin-transform-dotall-regex": "^7.12.1", - "@babel/plugin-transform-duplicate-keys": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-function-name": "^7.12.1", - "@babel/plugin-transform-literals": "^7.12.1", - "@babel/plugin-transform-member-expression-literals": "^7.12.1", - "@babel/plugin-transform-modules-amd": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-modules-systemjs": "^7.12.1", - "@babel/plugin-transform-modules-umd": "^7.12.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", - "@babel/plugin-transform-new-target": "^7.12.1", - "@babel/plugin-transform-object-super": "^7.12.1", - "@babel/plugin-transform-parameters": "^7.12.1", - "@babel/plugin-transform-property-literals": "^7.12.1", - "@babel/plugin-transform-regenerator": "^7.12.1", - "@babel/plugin-transform-reserved-words": "^7.12.1", - "@babel/plugin-transform-shorthand-properties": "^7.12.1", - "@babel/plugin-transform-spread": "^7.12.1", - "@babel/plugin-transform-sticky-regex": "^7.12.7", - "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.10", - "@babel/plugin-transform-unicode-escapes": "^7.12.1", - "@babel/plugin-transform-unicode-regex": "^7.12.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.11", - "core-js-compat": "^3.8.0", - "semver": "^5.5.0" + "@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/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, "node_modules/@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1393,77 +2119,87 @@ "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/preset-react": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz", - "integrity": "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-transform-react-display-name": "^7.12.13", - "@babel/plugin-transform-react-jsx": "^7.13.12", - "@babel/plugin-transform-react-jsx-development": "^7.12.17", - "@babel/plugin-transform-react-pure-annotations": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/runtime": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", - "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", - "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", - "dev": true, - "dependencies": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, "node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz", - "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.14.0", - "debug": "^4.1.0", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@babel/traverse/node_modules/ms": { @@ -1472,12 +2208,15 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@babel/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz", - "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@bcoe/v8-coverage": { @@ -1487,890 +2226,1393 @@ "dev": true }, "node_modules/@chakra-ui/accordion": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-1.3.4.tgz", - "integrity": "sha512-X+o68wcMkm07yWGjZz69rRke6W0zsD1eEG8uBs7iFy+q0sc1n5LiHNO/1L6s6CyBo6omI31RS/fbLD9OXJVD1g==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.3.1.tgz", + "integrity": "sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==", "dependencies": { - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/alert": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-1.2.6.tgz", - "integrity": "sha512-aq2hVHQFe3sFHYWDj+3HRVTKOqWlWwpm/FFihPVNoYteLKje8f71n3VN3rhDaFY15tFDXq9Uv3qTdMK55KXGlg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.2.2.tgz", + "integrity": "sha512-jHg4LYMRNOJH830ViLuicjb3F+v6iriE/2G5T+Sd0Hna04nukNJ1MxUmBPE+vI22me2dIflfelu2v9wdB6Pojw==", "dependencies": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, + "node_modules/@chakra-ui/anatomy": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==" + }, "node_modules/@chakra-ui/avatar": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-1.2.7.tgz", - "integrity": "sha512-WwtHDEmnSglBKOkxQHRu8tUtRTKu+vn35JlO6QVP+Mb5SPX0vFns3F38dohVr2s1wGUiMVMq/bt0JNCG5fFzhQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", "dependencies": { - "@chakra-ui/image": "1.0.17", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/image": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/breadcrumb": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-1.2.7.tgz", - "integrity": "sha512-gJVigaLRIkRCNBgH8B36fOFCgGIKErZOutchhIOCiycWnIStaGiZ7XpQIbuXCWHcLtWG3+YRL4pupx7mOPoc3w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", "dependencies": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" } }, "node_modules/@chakra-ui/button": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-1.4.1.tgz", - "integrity": "sha512-KnxG0buRMdM5KM1p00UozZ9KmZ22RKWUHvJrqtfi2Qxcj6FaEgS3nTXInLRpMIQ5xc83O07mio+pZ1j4zoRrbw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", "dependencies": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/spinner": "1.1.11", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@chakra-ui/checkbox": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-1.5.4.tgz", - "integrity": "sha512-exEfDZZK2IQjT4DpTYynC7wdUGWxBTo+iYfTmA/DOvcTW9RqETgYSJteRUTZdFgA3AptH1XN/PuAj/ucIsQ9VA==", + "node_modules/@chakra-ui/card": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.2.0.tgz", + "integrity": "sha512-xUB/k5MURj4CtPAhdSoXZidUbm8j3hci9vnc+eZJVDqhDOShNlD6QeniQNRPRys4lWAQLCbFcrwL29C8naDi6g==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.3.2.tgz", + "integrity": "sha512-85g38JIXMEv6M+AcyIGLh7igNtfpAN6KGQFYxY9tBj0eWvWk4NKQxvqqyVta0bSAyIl1rixNIIezNpNWk2iO4g==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.2.0", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/clickable": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-1.1.6.tgz", - "integrity": "sha512-wCA/QKXwJaB6t6DRfIk8tKRBkHMmgG3aqXD9/KusXb+3OGDExuxrcO/nBkpTwZJ0+y0FPADpOduLupnrHQ4KNw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", "dependencies": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/close-button": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-1.1.10.tgz", - "integrity": "sha512-DgjPZlqt2lixmLfnWaeqUQwGzRW3Ld1UNncjMzVUhTFxyfgSOCRLTQP4Hj4NWXilK3SuiPtxrtxAzm1sdYRfLg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", "dependencies": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/icon": "3.2.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/color-mode": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-1.1.10.tgz", - "integrity": "sha512-fMI4yeaWjlDwM9gsGpD4G23j/7aVL7UQcZmPnyTsyPXWM7Y51CO7VF8Nr7WCeq2l0axjhVqMs+HveL4biM+kGw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", + "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/control-box": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-1.0.14.tgz", - "integrity": "sha512-BJJQnOy0C6gDH1sbQTRYflaWdc0h3IafcGAD0d2WGYVscMicAiNd/+6qGfqivrCESpghz4pfDcNE96UIFUYvHg==", - "dependencies": { - "@chakra-ui/utils": "1.8.1" - }, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.1.0.tgz", + "integrity": "sha512-gVrRDyXFdMd8E7rulL0SKeoljkLQiPITFnsyMO8EFHNZ+AHt5wK4LIguYVEq88APqAGZGfHFWXr79RYrNiE3Mg==", "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/counter": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-1.1.7.tgz", - "integrity": "sha512-RrlbFg8u3UNcqPm7SLyJGLeqPnFuRqccXXL98Udy5wLhEe1maI6mUPu0bZHTm0VJ1AEdiVzbql0qH8HLneMiGg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/css-reset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.0.0.tgz", - "integrity": "sha512-UaPsImGHvCgFO3ayp6Ugafu2/3/EG8wlW/8Y9Ihfk1UFv8cpV+3BfWKmuZ7IcmxcBL9dkP6E8p3/M1T0FB92hg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.3.0.tgz", + "integrity": "sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==", "peerDependencies": { "@emotion/react": ">=10.0.35", - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/descendant": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-2.0.1.tgz", - "integrity": "sha512-TeYp94iOhu5Gs2oVzewJaep0qft/JKMKfmcf4PGgzJF+h6TWZm6NGohk6Jq7JOh+y0rExa1ulknIgnMzFx5xaA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", "dependencies": { - "@chakra-ui/react-utils": "^1.1.2" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, + "node_modules/@chakra-ui/dom-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.1.0.tgz", + "integrity": "sha512-ZmF2qRa1QZ0CMLU8M1zCfmw29DmPNtfjR9iTo74U5FPr3i1aoAh7fbJ4qAlZ197Xw9eAW28tvzQuoVWeL5C7fQ==" + }, "node_modules/@chakra-ui/editable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-1.2.7.tgz", - "integrity": "sha512-wmS5eGNw4ACX+kMEPxV97B6DEMJhGmvsUpdJAA8HDbDdcZNZk93Zkuog10X1cvXaddNCpDkFaa+TBOkqjeluNA==", - "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, + "node_modules/@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==" + }, "node_modules/@chakra-ui/focus-lock": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-1.1.9.tgz", - "integrity": "sha512-C6nQqn5PNOiwp6Ovd9xzJ2V6P3d3ZdfykTl+Fc4YdTC47LTrJzJmv61++nhDAzYeEseojmmgXIE1DlZfGjZpZQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.1.0.tgz", + "integrity": "sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==", "dependencies": { - "@chakra-ui/utils": "1.8.1", - "react-focus-lock": "2.5.0" + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/form-control": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-1.3.8.tgz", - "integrity": "sha512-S4zHu9ktuUeiqFC/ZM95UQ8CrnJvuXKfFRG+HsQrO5JjvaiYl0YjDE79Bi6+oj5WHjz0Zo7t+px+LAjxn7my3Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", + "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/hooks": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-1.5.4.tgz", - "integrity": "sha512-xAFj2Feu+ZWD1oxbQQ2UHDI7zbx/zZXjlS6ogdpXZoMrGYJhbdbV0JNGx4eK1Q1AEChNLdnZQIq8An1gYKgE8g==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", "dependencies": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "compute-scroll-into-view": "1.0.14", - "copy-to-clipboard": "3.3.1" + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "3.0.3", + "copy-to-clipboard": "3.3.3" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" + } + }, + "node_modules/@chakra-ui/hooks/node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/hooks/node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@chakra-ui/hooks/node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "dependencies": { + "tslib": "2.4.0" } }, + "node_modules/@chakra-ui/hooks/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, "node_modules/@chakra-ui/icon": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-1.1.10.tgz", - "integrity": "sha512-AZ2dKCHKT6dI4K9NXizHsNZSwPuBP0i1BZ4ZPoXGMOfNt7bD3yKBLoZfyO+NmAubMHanVASztikSNAmy2Rvczg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", "dependencies": { - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/icons": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-1.0.14.tgz", - "integrity": "sha512-VM21FkQc4rWcES1D6ddNIq6VYaCnTwWBIaqM9GRQZ7FpsLeVNk6UFYiE8MMtGWVIXq3k9jEYLbQHm7YdEF9yLQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", "dependencies": { - "@chakra-ui/icon": "1.1.10", - "@types/react": "^17.0.0" + "@chakra-ui/icon": "3.2.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/image": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-1.0.17.tgz", - "integrity": "sha512-M6OGT2Qs9Gy8Ba21XTWFDKe97fALSOSAcpQ38seSQt2hBjYdf8Pa3nKN6OO4O5zpTe612A/Sawuwxhf+6fSCeQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/input": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-1.2.8.tgz", - "integrity": "sha512-WGvkcjJH9XpOlpKI9POn7UDA8qnHf22mBKY771U3IfW2QxcZH/rPFwDE7YIMLr9M4g+rL4NLSWmXYvO92rzc6A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", "dependencies": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/layout": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-1.4.7.tgz", - "integrity": "sha512-wu1IBz/zg8rj4N88w4MtjS2kC5w+FXEvbxt0r2DqxLtPUFtE/fFmCa8OKsz+jMrDcZ1dRh48YNYrrWdAGEOQ8w==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.3.1.tgz", + "integrity": "sha512-nXuZ6WRbq0WdgnRgLw+QuxWAHuhDtVX8ElWqcTK+cSMFg/52eVP47czYBE5F35YhnoW2XBwfNoNgZ7+e8Z01Rg==", "dependencies": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, + "node_modules/@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==" + }, "node_modules/@chakra-ui/live-region": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-1.0.13.tgz", - "integrity": "sha512-bzgi8jIYxVaqSVmUynnGFDjBOKf1LuKY1qMljuwIa7rK6iJZiMxTAdPbxX5Km4xTdgUz5AtZrmqDvKKLPDA1fg==", - "dependencies": { - "@chakra-ui/utils": "1.8.1" - }, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.1.0.tgz", + "integrity": "sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==", "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/media-query": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-1.1.1.tgz", - "integrity": "sha512-KHsY4NzMl77yMyqpw3nleh1xM3zqAhCmSRBzQIh5fU/kT7r2tCwGl53djY5O2pl9VPMb4LhqPwkNd6vsscfCxQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.3.0.tgz", + "integrity": "sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==", "dependencies": { - "@chakra-ui/react-env": "1.0.5", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "@chakra-ui/theme": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/menu": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-1.7.1.tgz", - "integrity": "sha512-a9+iyw+cUBtxC/+mKAhPS92a0Nlq94wXpz8haswWTNSOLE5U/zXNDbiG8BsXQ+pS8ngPUjZRE35EFSge+efV8Q==", - "dependencies": { - "@chakra-ui/clickable": "1.1.6", - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.2.1.tgz", + "integrity": "sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-outside-click": "2.2.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/modal": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-1.8.9.tgz", - "integrity": "sha512-fguU4zpE/4JWKY0yHyi/PoM0QzcBokgcT3KZnZj3KGOc1C15ZkR6GvD5UBubGMWQzlKT9hCwYaLc+VeoHnN6XA==", - "dependencies": { - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/focus-lock": "1.1.9", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/portal": "1.2.7", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.4.1" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0", + "aria-hidden": "^1.2.3", + "react-remove-scroll": "^2.5.6" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" } }, "node_modules/@chakra-ui/number-input": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-1.2.8.tgz", - "integrity": "sha512-f8mQrPJu7O5qX4auNu24N6TtzaAE/q+eld1K+vwVdFUeFCOxuSsEoMT3xOEPrkEKYtikFDt0Dy3+pYrTcgBrvA==", - "dependencies": { - "@chakra-ui/counter": "1.1.7", - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", + "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", + "dependencies": { + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-interval": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, + "node_modules/@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==" + }, + "node_modules/@chakra-ui/object-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==" + }, "node_modules/@chakra-ui/pin-input": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-1.6.3.tgz", - "integrity": "sha512-BZYNUpcwagjfAr8olmkZe5aQ3e45q4rwoIwWvHVb39KVvPP3L7jzLFlxzoncoxVfBh9hOEztg/GeIeN0arLtLw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.1.0.tgz", + "integrity": "sha512-x4vBqLStDxJFMt+jdAHHS8jbh294O53CPQJoL4g228P513rHylV/uPscYUHrVJXRxsHfRztQO9k45jjTYaPRMw==", "dependencies": { - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/popover": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-1.8.1.tgz", - "integrity": "sha512-fEYcEV6rO4H9ewj+8nom5flHZfh8+BwxNfuzVZFnJbzuSzP9NKk5VMp+nbBow2CKlI/ct3Y8dpaLbsYrm/X6AA==", - "dependencies": { - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.2.1.tgz", + "integrity": "sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/popper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-2.2.1.tgz", - "integrity": "sha512-W0hMTBp2X62UooF3qPNmsEW0IJfz72gr2DN8nsCvHQrMiARB9s2jECEss6qEsB97tnmIG8k2TNee8IzTGLmMyA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", "dependencies": { - "@chakra-ui/react-utils": "1.1.2", - "@popperjs/core": "2.4.4" + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@popperjs/core": "^2.9.3" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, "node_modules/@chakra-ui/portal": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-1.2.7.tgz", - "integrity": "sha512-s5iFEhjZ1r5cyIH3i5R6UOW5FwmM3JDFkLw3Y7wumlYV4CscV2/UwoKIbscR93COMGP+HPvfVDUZOB1woftQRA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" }, "peerDependencies": { - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "react": ">=18", + "react-dom": ">=18" } }, "node_modules/@chakra-ui/progress": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-1.1.11.tgz", - "integrity": "sha512-8cPvHI/TxQSP1DPs7nC1qnLPFFd2lzMs7GDk0AcORW+Be8BS0cJC5NV9wZJM4N8RUP4sK4nhkMfyq4GbrNzoLg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/provider": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.4.2.tgz", + "integrity": "sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==", "dependencies": { - "@chakra-ui/theme-tools": "1.1.8", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/utils": "2.0.15" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/provider/node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/provider/node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@chakra-ui/provider/node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "dependencies": { + "tslib": "2.4.0" } }, + "node_modules/@chakra-ui/provider/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, "node_modules/@chakra-ui/radio": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-1.3.8.tgz", - "integrity": "sha512-3HWS7OVrdtqZYR/FBtIQhVvVLU0hiWZWWdiG+W1g6V3YhTq1PtwDA8uYDDe5KxaA/DjXfUhg1mQjjozgB1jZ/g==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-n10M46wJrMGbonaghvSRnZ9ToTv/q76Szz284gv4QUWvyljQACcGrXIONUnQ3BIwbOfkRqSk7Xl/JgZtVfll+w==", "dependencies": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.16.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, "node_modules/@chakra-ui/react": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-1.6.5.tgz", - "integrity": "sha512-kvBNX3gkg2CCbdaj585I8m7Wd+PGMLTpEM15WbII3t6E26lhKWwD5OXMomhWhsnBMCM9uSQ790dunhffcruUUg==", - "dependencies": { - "@chakra-ui/accordion": "1.3.4", - "@chakra-ui/alert": "1.2.6", - "@chakra-ui/avatar": "1.2.7", - "@chakra-ui/breadcrumb": "1.2.7", - "@chakra-ui/button": "1.4.1", - "@chakra-ui/checkbox": "1.5.4", - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/control-box": "1.0.14", - "@chakra-ui/counter": "1.1.7", - "@chakra-ui/css-reset": "1.0.0", - "@chakra-ui/editable": "1.2.7", - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/image": "1.0.17", - "@chakra-ui/input": "1.2.8", - "@chakra-ui/layout": "1.4.7", - "@chakra-ui/live-region": "1.0.13", - "@chakra-ui/media-query": "1.1.1", - "@chakra-ui/menu": "1.7.1", - "@chakra-ui/modal": "1.8.9", - "@chakra-ui/number-input": "1.2.8", - "@chakra-ui/pin-input": "1.6.3", - "@chakra-ui/popover": "1.8.1", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/portal": "1.2.7", - "@chakra-ui/progress": "1.1.11", - "@chakra-ui/radio": "1.3.8", - "@chakra-ui/react-env": "1.0.5", - "@chakra-ui/select": "1.1.12", - "@chakra-ui/skeleton": "1.1.16", - "@chakra-ui/slider": "1.2.7", - "@chakra-ui/spinner": "1.1.11", - "@chakra-ui/stat": "1.1.11", - "@chakra-ui/switch": "1.2.7", - "@chakra-ui/system": "1.7.1", - "@chakra-ui/table": "1.2.5", - "@chakra-ui/tabs": "1.5.3", - "@chakra-ui/tag": "1.1.11", - "@chakra-ui/textarea": "1.1.12", - "@chakra-ui/theme": "1.9.2", - "@chakra-ui/toast": "1.2.9", - "@chakra-ui/tooltip": "1.3.8", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.8.2.tgz", + "integrity": "sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==", + "dependencies": { + "@chakra-ui/accordion": "2.3.1", + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/avatar": "2.3.0", + "@chakra-ui/breadcrumb": "2.2.0", + "@chakra-ui/button": "2.1.0", + "@chakra-ui/card": "2.2.0", + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/control-box": "2.1.0", + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/editable": "3.1.0", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/hooks": "2.2.1", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/image": "2.1.0", + "@chakra-ui/input": "2.1.2", + "@chakra-ui/layout": "2.3.1", + "@chakra-ui/live-region": "2.1.0", + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/menu": "2.2.1", + "@chakra-ui/modal": "2.3.1", + "@chakra-ui/number-input": "2.1.2", + "@chakra-ui/pin-input": "2.1.0", + "@chakra-ui/popover": "2.2.1", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/progress": "2.2.0", + "@chakra-ui/provider": "2.4.2", + "@chakra-ui/radio": "2.1.2", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/select": "2.1.2", + "@chakra-ui/skeleton": "2.1.0", + "@chakra-ui/skip-nav": "2.1.0", + "@chakra-ui/slider": "2.1.0", + "@chakra-ui/spinner": "2.1.0", + "@chakra-ui/stat": "2.1.1", + "@chakra-ui/stepper": "2.3.1", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/switch": "2.1.2", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/table": "2.1.0", + "@chakra-ui/tabs": "3.0.0", + "@chakra-ui/tag": "3.1.1", + "@chakra-ui/textarea": "2.1.2", + "@chakra-ui/theme": "3.3.1", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/toast": "7.0.2", + "@chakra-ui/tooltip": "2.3.1", + "@chakra-ui/transition": "2.1.0", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.2.0" }, "peerDependencies": { "@emotion/react": "^11.0.0", "@emotion/styled": "^11.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-context": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", + "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", + "peerDependencies": { + "react": ">=18" } }, "node_modules/@chakra-ui/react-env": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-1.0.5.tgz", - "integrity": "sha512-qAWslmm27q7DyHv5XvIoW6ihmilQK6K/LNc0bUlPrKaxzLtk9m16N767spl+xue9JyPb7ZE3gAPwdUEUD7XKhQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.1.0.tgz", + "integrity": "sha512-Vr96GV2LNBth3+IKzr/rq1IcnkXv+MLmwjQH6C8BRtn3sNskgDFD5vLkVXcEhagzZMCh8FR3V/bzZPojBOyNhw==", "dependencies": { - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" }, "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/react-utils": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-1.1.2.tgz", - "integrity": "sha512-S8jPVKGZH2qF7ZGxl/0DF/dXXI2AxDNGf4Ahi2LGHqajMvqBB7vtYIRRmIA7+jAnErhzO8WUi3i4Z7oScp6xSA==", - "dependencies": { - "@chakra-ui/utils": "^1.7.0" - }, + "node_modules/@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/select": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-1.1.12.tgz", - "integrity": "sha512-oOCLLCONoGgnJ/RvWEvdl+ggecDGIlxYHOsTjPu2vZs6PPIer69Xf9/S36Zp4kkuYWxz2ssK3YMoiU0PpPz7GQ==", + "node_modules/@chakra-ui/react-use-animation-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.1.0.tgz", + "integrity": "sha512-CFZkQU3gmDBwhqy0vC1ryf90BVHxVN8cTLpSyCpdmExUEtSEInSCGMydj2fvn7QXsz/za8JNdO2xxgJwxpLMtg==", "dependencies": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/skeleton": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-1.1.16.tgz", - "integrity": "sha512-pzqa2PYg21ktFrdIcMvx+BEG4u+tTNuHDHqQeFD7bV7tYbNkMlQhY7I7kTBWMo0mROmnrerVBTJd92CbG/c5lA==", - "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/media-query": "1.1.1", - "@chakra-ui/system": "1.7.1", - "@chakra-ui/utils": "1.8.1" - }, + "node_modules/@chakra-ui/react-use-callback-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", + "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", "peerDependencies": { - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/slider": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-1.2.7.tgz", - "integrity": "sha512-fp5ef8MEbXq89U4TpSeEa6NUwvtSyHbM6VSdZCgsHG546BWpRkcCEvagtKXmviX4NthtOyig0YCqmET8HKduVA==", + "node_modules/@chakra-ui/react-use-controllable-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.1.0.tgz", + "integrity": "sha512-QR/8fKNokxZUs4PfxjXuwl0fj/d71WPrmLJvEpCTkHjnzu7LnYvzoe2wB867IdooQJL0G1zBxl0Dq+6W1P3jpg==", "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/spinner": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-1.1.11.tgz", - "integrity": "sha512-gkh44jZ8msfHQgswVvflbWz/6Egv5FeSu6a7BJWX/XQJw9IxPy0B75xy0d06LgQCOFk17x2xhB+mwZI6i55T8Q==", + "node_modules/@chakra-ui/react-use-disclosure": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.1.0.tgz", + "integrity": "sha512-Ax4pmxA9LBGMyEZJhhUZobg9C0t3qFE4jVF1tGBsrLDcdBeLR9fwOogIPY9Hf0/wqSlAryAimICbr5hkpa5GSw==", "dependencies": { - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/stat": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-1.1.11.tgz", - "integrity": "sha512-47aHxoAReUmQ0bU6q7qY2N9RryKtZWTheK/xepFppGI5Q0hWSoOESkJ8BNZ/LuQW6NLCmv2jOxyhW4XIDEJ+fA==", + "node_modules/@chakra-ui/react-use-event-listener": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", + "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", "dependencies": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/styled-system": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-1.12.1.tgz", - "integrity": "sha512-/92egMOe6/6xerCmoos1/HhZBJdeRwIRa2BR+wwkHJ4ehqxi4IBtU9oXc2g4P70GGh6UqKIgR/oURrvVY8vjow==", + "node_modules/@chakra-ui/react-use-focus-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.1.0.tgz", + "integrity": "sha512-xzVboNy7J64xveLcxTIJ3jv+lUJKDwRM7Szwn9tNzUIPD94O3qwjV7DDCUzN2490nSYDF4OBMt/wuDBtaR3kUQ==", "dependencies": { - "@chakra-ui/utils": "1.8.1", - "csstype": "^3.0.6" + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@chakra-ui/switch": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-1.2.7.tgz", - "integrity": "sha512-zHI6lg+NuDUw9vxEDSOkH4j2lRntIpwysuIEYUKFPkH2zmZpo6c1zLA9L+rfMbqFRoewm+YIqh8tOgQmNbIGPg==", + "node_modules/@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.1.0.tgz", + "integrity": "sha512-2jzrUZ+aiCG/cfanrolsnSMDykCAbv9EK/4iUyZno6BYb3vziucmvgKuoXbMPAzWNtwUwtuMhkby8rc61Ue+Lg==", "dependencies": { - "@chakra-ui/checkbox": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-event-listener": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/system": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-1.7.1.tgz", - "integrity": "sha512-1G7+mAPbkGqtowZ4Bt9JwCB2wTJt701vj/vPLRW2KDYqlES5Xp2RomG8LdrGQcVWfiwO2wzpCYUZj2YLY4kbVA==", + "node_modules/@chakra-ui/react-use-interval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", + "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", "dependencies": { - "@chakra-ui/color-mode": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/styled-system": "1.12.1", - "@chakra-ui/utils": "1.8.1", - "react-fast-compare": "3.2.0" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, "peerDependencies": { - "@emotion/react": "^11.0.0", - "@emotion/styled": "^11.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/system/node_modules/react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + "node_modules/@chakra-ui/react-use-latest-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.1.0.tgz", + "integrity": "sha512-m0kxuIYqoYB0va9Z2aW4xP/5b7BzlDeWwyXCH6QpT2PpW3/281L3hLCm1G0eOUcdVlayqrQqOeD6Mglq+5/xoQ==", + "peerDependencies": { + "react": ">=18" + } }, - "node_modules/@chakra-ui/table": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-1.2.5.tgz", - "integrity": "sha512-iYSDv4oTKZ8bLJo9OHjAPCi7cxDXXVXIYupwP2oXcBsM8Hx6FrmlPlO8vdBCTD2ySaazFOZgW2/EPOKsXlAnlQ==", - "dependencies": { - "@chakra-ui/utils": "1.8.1" - }, + "node_modules/@chakra-ui/react-use-merge-refs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", + "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/tabs": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-1.5.3.tgz", - "integrity": "sha512-Nn/+gSZRigODwPK597U6DYwaPiOZAFNsozE5RYSZootr/tMIwqTh3opxwzW9zbPx4lQ2+3uvS4QHN5Tn+YxW8Q==", + "node_modules/@chakra-ui/react-use-outside-click": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.2.0.tgz", + "integrity": "sha512-PNX+s/JEaMneijbgAM4iFL+f3m1ga9+6QK0E5Yh4s8KZJQ/bLwZzdhMz8J/+mL+XEXQ5J0N8ivZN28B82N1kNw==", "dependencies": { - "@chakra-ui/clickable": "1.1.6", - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/tag": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-1.1.11.tgz", - "integrity": "sha512-XLKafTuK5lsRLk+zAXCQZ1368GOTf59ghtpYofLg0ieGAbOOuNmw1/lLKdnrnHj8ueatKPr86bDa4DQ31J3Lxg==", + "node_modules/@chakra-ui/react-use-pan-event": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.1.0.tgz", + "integrity": "sha512-xmL2qOHiXqfcj0q7ZK5s9UjTh4Gz0/gL9jcWPA6GVf+A0Od5imEDa/Vz+533yQKWiNSm1QGrIj0eJAokc7O4fg==", "dependencies": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "framesync": "6.1.2" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/textarea": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-1.1.12.tgz", - "integrity": "sha512-Qmc98ePiSdjCJ/AVCQ6mgX7Ez/cEoBTPkP/t4eqbjpfBSWYAExfYn/w/Tkcx1C5dd9cfk+EPzxM2r3KVpWuQGA==", + "node_modules/@chakra-ui/react-use-pan-event/node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", "dependencies": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/utils": "1.8.1" - }, + "tslib": "2.4.0" + } + }, + "node_modules/@chakra-ui/react-use-pan-event/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@chakra-ui/react-use-previous": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.1.0.tgz", + "integrity": "sha512-pjxGwue1hX8AFcmjZ2XfrQtIJgqbTF3Qs1Dy3d1krC77dEsiCUbQ9GzOBfDc8pfd60DrB5N2tg5JyHbypqh0Sg==", "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/theme": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-1.9.2.tgz", - "integrity": "sha512-bSKcVGTi83sjdQNJULLAul0mL3Hljs+KEZ+oWEl0FogPumCeBOBW4rPCnddW3YWkQUrHwoNz4hag29klTs/IsQ==", - "dependencies": { - "@chakra-ui/theme-tools": "1.1.8", - "@chakra-ui/utils": "1.8.1" - }, + "node_modules/@chakra-ui/react-use-safe-layout-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", + "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", "peerDependencies": { - "@chakra-ui/system": ">=1.0.0" + "react": ">=18" } }, - "node_modules/@chakra-ui/theme-tools": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-1.1.8.tgz", - "integrity": "sha512-FQqHNfuvl2O1m7o6YY3ozqxnz74TWAhVzzfKrh7/eXcyA2IkF+MuKMUnyWXjOq1bcLt9rAGq0FQALisTd4YPWQ==", + "node_modules/@chakra-ui/react-use-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.1.0.tgz", + "integrity": "sha512-tbLqrQhbnqOjzTaMlYytp7wY8BW1JpL78iG7Ru1DlV4EWGiAmXFGvtnEt9HftU0NJ0aJyjgymkxfVGI55/1Z4A==", "dependencies": { - "@chakra-ui/utils": "1.8.1", - "@types/tinycolor2": "1.4.2", - "tinycolor2": "1.4.2" + "@zag-js/element-size": "0.10.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0" + "react": ">=18" } }, - "node_modules/@chakra-ui/toast": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-1.2.9.tgz", - "integrity": "sha512-fVE5UD27WykiPS817Wlee4LAT01SysWFxCFikflBj1nK8UJXhRKV/UavNf5aJbxvzx5QCwkD0pjFmDO9uxOSPA==", + "node_modules/@chakra-ui/react-use-timeout": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.1.0.tgz", + "integrity": "sha512-cFN0sobKMM9hXUhyCofx3/Mjlzah6ADaEl/AXl5Y+GawB5rgedgAcu2ErAgarEkwvsKdP6c68CKjQ9dmTQlJxQ==", "dependencies": { - "@chakra-ui/alert": "1.2.6", - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/theme": "1.9.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1", - "@reach/alert": "0.13.2" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/tooltip": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-1.3.8.tgz", - "integrity": "sha512-7rqAhcd04ZnnJZ2DmGvVPNyi/+Fy4bzQocYn83rWR3LC/8/LM+czG6pmz4FKjYR5iU6Ttf6Ckp8NfFKhyHAp/g==", - "dependencies": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/portal": "1.2.7", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" - }, + "node_modules/@chakra-ui/react-use-update-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", + "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/transition": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-1.3.3.tgz", - "integrity": "sha512-p9ZRaHNdSGQKS3trL7jSxh47fQDDEZfgYHMx7L/mDy6vxMNsO6YhnURULePk90hvtCAp6Z4urNTM6VYaywioQQ==", + "node_modules/@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", "dependencies": { - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/utils": "2.0.15" }, "peerDependencies": { - "framer-motion": "3.x || 4.x", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/utils": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-1.8.1.tgz", - "integrity": "sha512-v0xL9U2ozDbHCl2kQTdJNOjUGT7ZjyFwEYuMW02ZaLkmLPj2w3G592iOsJ9Z9sBemQgoOrZGyTWqdxm6rhxJug==", + "node_modules/@chakra-ui/react-utils/node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", "dependencies": { - "@types/lodash.mergewith": "4.6.6", + "@types/lodash.mergewith": "4.6.7", "css-box-model": "1.2.1", - "framesync": "5.3.0", + "framesync": "6.1.2", "lodash.mergewith": "4.6.2" } }, - "node_modules/@chakra-ui/visually-hidden": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-1.0.13.tgz", - "integrity": "sha512-wFFXdejxwOT7r7AbD/IFl6Ve+n6VIOl2Drjcrn3JXmfwzL9NKB3xrtcdMXe8G/zW9jRXh+E6DUkTyEUjdUZErg==", + "node_modules/@chakra-ui/react-utils/node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", "dependencies": { - "@chakra-ui/utils": "1.8.1" - }, - "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "react": ">=16.8.6" + "@types/lodash": "*" } }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, + "node_modules/@chakra-ui/react-utils/node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" + "tslib": "2.4.0" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", - "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } + "node_modules/@chakra-ui/react-utils/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, - "node_modules/@emotion/babel-plugin": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.3.0.tgz", - "integrity": "sha512-UZKwBV2rADuhRp+ZOGgNWg2eYgbzKzQXfQPtJbu/PLy8onurxlNCLvxMQEvlr1/GudguPI5IU9qIY1+2z1M5bA==", - "dependencies": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/runtime": "^7.13.10", - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.5", - "@emotion/serialize": "^1.0.2", - "babel-plugin-macros": "^2.6.1", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "^4.0.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node_modules/@chakra-ui/react/node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" } }, - "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", - "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" - }, - "node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", - "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "node_modules/@chakra-ui/react/node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", "dependencies": { - "@babel/runtime": "^7.7.2", - "cosmiconfig": "^6.0.0", - "resolve": "^1.12.0" + "@types/lodash": "*" } }, - "node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "node_modules/@chakra-ui/react/node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "engines": { - "node": ">=8" + "tslib": "2.4.0" } }, - "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "node_modules/@chakra-ui/react/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@chakra-ui/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==" + }, + "node_modules/@chakra-ui/skeleton": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", + "dependencies": { + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/skip-nav": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skip-nav/-/skip-nav-2.1.0.tgz", + "integrity": "sha512-Hk+FG+vadBSH0/7hwp9LJnLjkO0RPGnx7gBJWI4/SpoJf3e4tZlWYtwGj0toYY4aGKl93jVghuwGbDBEMoHDug==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-lUOBcLMCnFZiA/s2NONXhELJh6sY5WtbRykPtclGfynqqOo47lwWJx+VP7xaeuhDOPcWSSecWc9Y1BfPOCz9cQ==", + "dependencies": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-pan-event": "2.1.0", + "@chakra-ui/react-use-size": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/spinner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.1.0.tgz", + "integrity": "sha512-hczbnoXt+MMv/d3gE+hjQhmkzLiKuoTo42YhUG7Bs9OSv2lg1fZHW1fGNRFP3wTi6OIbD044U1P9HK+AOgFH3g==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stepper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.3.1.tgz", + "integrity": "sha512-ky77lZbW60zYkSXhYz7kbItUpAQfEdycT0Q4bkHLxfqbuiGMf8OmgZOQkOB9uM4v0zPwy2HXhe0vq4Dd0xa55Q==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/styled-system": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", + "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/switch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.1.2.tgz", + "integrity": "sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==", + "dependencies": { + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/system": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", + "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", + "dependencies": { + "@chakra-ui/color-mode": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/system/node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/system/node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@chakra-ui/system/node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "dependencies": { + "tslib": "2.4.0" + } + }, + "node_modules/@chakra-ui/system/node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/@chakra-ui/system/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@chakra-ui/table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.1.0.tgz", + "integrity": "sha512-o5OrjoHCh5uCLdiUb0Oc0vq9rIAeHSIRScc2ExTC9Qg/uVZl2ygLrjToCaKfaaKl1oQexIeAcZDKvPG8tVkHyQ==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-3.0.0.tgz", + "integrity": "sha512-6Mlclp8L9lqXmsGWF5q5gmemZXOiOYuh0SGT/7PgJVNPz3LXREXlXg2an4MBUD8W5oTkduCX+3KTMCwRrVrDYw==", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tag": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.1.1.tgz", + "integrity": "sha512-Bdel79Dv86Hnge2PKOU+t8H28nm/7Y3cKd4Kfk9k3lOpUh4+nkSGe58dhRzht59lEqa4N9waCgQiBdkydjvBXQ==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/textarea": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.1.2.tgz", + "integrity": "sha512-ip7tvklVCZUb2fOHDb23qPy/Fr2mzDOGdkrpbNi50hDCiV4hFX02jdQJdi3ydHZUyVgZVBKPOJ+lT9i7sKA2wA==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/theme": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", + "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.8.0" + } + }, + "node_modules/@chakra-ui/theme-tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", + "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.0.0" + } + }, + "node_modules/@chakra-ui/theme-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", + "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/toast": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-7.0.2.tgz", + "integrity": "sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==", + "dependencies": { + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-timeout": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1" + }, + "peerDependencies": { + "@chakra-ui/system": "2.6.2", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/tooltip": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.3.1.tgz", + "integrity": "sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/transition": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.1.0.tgz", + "integrity": "sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/visually-hidden": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.2.0.tgz", + "integrity": "sha512-KmKDg01SrQ7VbTD3+cPWf/UfpF5MSwm3v7MWi0n5t8HnnadT13MF0MJCDSXbBWnzLv1ZKJ6zlyAOeARWX+DpjQ==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "dependencies": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + }, + "bin": { + "watch": "cli.js" + }, + "engines": { + "node": ">=0.1.95" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", + "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", @@ -2382,21 +3624,26 @@ } }, "node_modules/@emotion/cache": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.4.0.tgz", - "integrity": "sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", "dependencies": { - "@emotion/memoize": "^0.7.4", - "@emotion/sheet": "^1.0.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "stylis": "^4.0.3" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" } }, + "node_modules/@emotion/cache/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, "node_modules/@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", @@ -2410,19 +3657,20 @@ "node_modules/@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true }, "node_modules/@emotion/react": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.4.0.tgz", - "integrity": "sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@emotion/cache": "^11.4.0", - "@emotion/serialize": "^1.0.2", - "@emotion/sheet": "^1.0.1", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.0.tgz", + "integrity": "sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.0", + "@emotion/cache": "^11.10.0", + "@emotion/serialize": "^1.1.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -2439,32 +3687,37 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", - "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", "dependencies": { - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.4", - "@emotion/unitless": "^0.7.5", - "@emotion/utils": "^1.0.0", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", "csstype": "^3.0.2" } }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, "node_modules/@emotion/sheet": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.1.tgz", - "integrity": "sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "node_modules/@emotion/styled": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.3.0.tgz", - "integrity": "sha512-fUoLcN3BfMiLlRhJ8CuPUMEyKkLEoM+n+UyAbnqGEsCd5IzKQ7VQFLtzpJOaCD2/VR2+1hXQTnSZXVJeiTNltA==", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.0.tgz", + "integrity": "sha512-V9oaEH6V4KePeQpgUE83i8ht+4Ri3E8Djp/ZPJ4DQlqWhSKITvgzlR3/YQE2hdfP4Jw3qVRkANJz01LLqK9/TA==", "dependencies": { - "@babel/runtime": "^7.13.10", - "@emotion/babel-plugin": "^11.3.0", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/serialize": "^1.0.2", - "@emotion/utils": "^1.0.0" + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.0", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.0", + "@emotion/utils": "^1.2.0" }, "peerDependencies": { "@babel/core": "^7.0.0", @@ -2481,747 +3734,974 @@ } }, "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.0.tgz", - "integrity": "sha512-9RkilvXAufQHsSsjQ3PIzSns+pxuX4EW8EbGeSPjZMHuMx6z/MOzb9LpqNieQX4F3mre3NWS2+X3JNRHTQztUQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", "dependencies": { - "@emotion/memoize": "^0.7.4" + "@emotion/memoize": "^0.8.0" } }, + "node_modules/@emotion/styled/node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, "node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" }, "node_modules/@emotion/utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz", - "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" }, "node_modules/@emotion/weak-memoize": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" - }, - "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - }, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "cosmiconfig": ">=6" + "node": ">=18" } }, - "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - }, - "node_modules/@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ms": "2.1.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 4" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@graphql-tools/mock": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.1.3.tgz", - "integrity": "sha512-xtY3amuEdPLeoSALNN4cEaOmietbVaxFAVfkn08v0AHr7zfXyy+sCLn98y8BXxTaow8/nTMBCTdCZ5Qe9gtbQQ==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@graphql-tools/schema": "^7.0.0", - "@graphql-tools/utils": "^7.0.0", - "fast-json-stable-stringify": "^2.1.0", - "ts-is-defined": "^1.0.0", - "tslib": "~2.2.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@graphql-tools/mock/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@graphql-tools/schema/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-tools/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@ardatan/aggregate-error": "0.0.6", - "camel-case": "4.1.2", - "tslib": "~2.2.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@graphql-tools/utils/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/@graphql-typed-document-node/core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz", - "integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==" - }, - "node_modules/@hot-loader/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-EN9czvcLsMYmSDo5yRKZOAq3ZGRlDpad1gPtX0NdMMomJXcPE3yFSeFzE94X/NjOaiSVimB7LuqPYpkWVaIi4Q==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@hot-loader/react-dom/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 10.14.2" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">= 10.14.2" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=8" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ms": "2.1.2" }, "engines": { - "node": ">=10" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" + }, + "node_modules/@graphql-tools/merge": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.3.3.tgz", + "integrity": "sha512-EfULshN2s2s2mhBwbV9WpGnoehRLe7eIMdZrKfHhxlBWOvtNUd3KSCN0PUdAMd7lj1jXUW9KYdn624JrVn6qzg==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "@graphql-tools/utils": "8.10.0", + "tslib": "^2.4.0" }, - "bin": { - "rimraf": "bin.js" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-yI+V373FdXQbYfqdarehn9vRWDZZYuvyQ/xwiv5ez2BbobHrqsexF7qs56plLRaQ8ESYpVAjMQvJWe9s23O0Jg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "tslib": "^2.4.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "node_modules/@graphql-tools/merge/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/@graphql-tools/mock": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.7.3.tgz", + "integrity": "sha512-U9e3tZenFvSTf0TAaFgwqO84cGNEbgzWXvboqJPth873dMag8sOlLyOBZceVzAZP7ptwfLbhm3S0Qq4ffI7mCw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" + "@graphql-tools/schema": "9.0.1", + "@graphql-tools/utils": "8.10.0", + "fast-json-stable-stringify": "^2.1.0", + "tslib": "^2.4.0" }, - "engines": { - "node": ">= 10.14.2" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/schema": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.1.tgz", + "integrity": "sha512-Y6apeiBmvXEz082IAuS/ainnEEQrzMECP1MRIV72eo2WPa6ZtLYPycvIbd56Z5uU2LKP4XcWRgK6WUbCyN16Rw==", "dev": true, "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "@graphql-tools/merge": "8.3.3", + "@graphql-tools/utils": "8.10.0", + "tslib": "^2.4.0", + "value-or-promise": "1.0.11" }, - "engines": { - "node": ">= 10.14.2" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/utils": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-yI+V373FdXQbYfqdarehn9vRWDZZYuvyQ/xwiv5ez2BbobHrqsexF7qs56plLRaQ8ESYpVAjMQvJWe9s23O0Jg==", "dev": true, "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" + "tslib": "^2.4.0" }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/mock/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/@graphql-tools/mock/node_modules/value-or-promise": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", + "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==", + "dev": true, "engines": { - "node": ">= 10.14.2" + "node": ">=12" } }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "node_modules/@graphql-tools/schema": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.0.tgz", + "integrity": "sha512-kf3qOXMFcMs2f/S8Y3A8fm/2w+GaHAkfr3Gnhh2LOug/JgpY/ywgFVxO3jOeSpSEdoYcDKLcXVjMigNbY4AdQg==", "dev": true, "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "engines": { - "node": ">= 10.14.2" + "node": ">=16.0.0" }, - "optionalDependencies": { - "node-notifier": "^8.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/merge": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.0.tgz", + "integrity": "sha512-J7/xqjkGTTwOJmaJQJ2C+VDBDOWJL3lKrHJN4yMaRLAJH3PosB7GiPRaSDZdErs0+F77sH2MKs2haMMkywzx7Q==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, + "node_modules/@graphql-tools/schema/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, + "node_modules/@graphql-tools/utils": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.4.tgz", + "integrity": "sha512-MF+nZgGROSnFgyOYWhrl2PuJMlIBvaCH48vtnlnDQKSeDc2fUfOzUVloBAQvnYmK9JBmHHks4Pxv25Ybg3r45Q==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@graphql-typed-document-node/core": "^3.1.1", + "dset": "^3.1.2", + "tslib": "^2.4.0" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@graphql-tools/utils/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=7.0.0" + "node": ">=10.10.0" } }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/@jest/transform": { + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", "dev": true, "dependencies": { - "@babel/core": "^7.1.0", "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", + "@types/node": "*", "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", + "jest-message-util": "^26.6.2", "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "slash": "^3.0.0" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { + "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -3233,7 +4713,7 @@ "node": ">=8" } }, - "node_modules/@jest/transform/node_modules/chalk": { + "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -3246,7 +4726,7 @@ "node": ">=10" } }, - "node_modules/@jest/transform/node_modules/color-convert": { + "node_modules/@jest/console/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -3258,13 +4738,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/transform/node_modules/color-name": { + "node_modules/@jest/console/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@jest/transform/node_modules/has-flag": { + "node_modules/@jest/console/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -3273,16 +4753,7 @@ "node": ">=8" } }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { + "node_modules/@jest/console/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -3294,32 +4765,46 @@ "node": ">=8" } }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "node_modules/@jest/core": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", + "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/console": "^26.6.2", + "@jest/reporters": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.6.2", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-resolve-dependencies": "^26.6.3", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "jest-watcher": "^26.6.2", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/@jest/types/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { + "node_modules/@jest/core/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -3331,7 +4816,7 @@ "node": ">=8" } }, - "node_modules/@jest/types/node_modules/chalk": { + "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -3344,7 +4829,7 @@ "node": ">=10" } }, - "node_modules/@jest/types/node_modules/color-convert": { + "node_modules/@jest/core/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -3356,13 +4841,13 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/types/node_modules/color-name": { + "node_modules/@jest/core/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@jest/types/node_modules/has-flag": { + "node_modules/@jest/core/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -3371,7 +4856,19 @@ "node": ">=8" } }, - "node_modules/@jest/types/node_modules/supports-color": { + "node_modules/@jest/core/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -3383,78 +4880,91 @@ "node": ">=8" } }, - "node_modules/@josephg/resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", - "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==", - "dev": true + "node_modules/@jest/environment": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", + "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } }, - "node_modules/@lingui/babel-plugin-extract-messages": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.10.2.tgz", - "integrity": "sha512-RBAw8mE/l6VtpunmgRllX2kotjvWhlo5skyySipQDmYRU7QWQV7z3F67UtaIyG4yHMqcdF3T/q9x45qcgtdDMw==", + "node_modules/@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", "dev": true, "dependencies": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.10.2", - "mkdirp": "^1.0.4" + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" }, "engines": { - "node": ">=10.0.0" + "node": ">= 10.14.2" } }, - "node_modules/@lingui/cli": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.10.2.tgz", - "integrity": "sha512-kuKMSeWyRYh3aw1zZZkqOzt2wjy1TdObji+PDg1OyoOgWkvap1SlpcFQ76Fgn2u1e6MOyDpfwg+I7fGEmOA+gQ==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.11.6", - "@babel/parser": "^7.11.5", - "@babel/plugin-syntax-jsx": "^7.10.4", - "@babel/runtime": "^7.11.2", - "@babel/types": "^7.11.5", - "@lingui/babel-plugin-extract-messages": "^3.10.2", - "@lingui/conf": "^3.10.2", - "babel-plugin-macros": "^3.0.1", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", - "chokidar": "3.5.1", - "cli-table": "^0.3.1", - "commander": "^6.1.0", - "date-fns": "^2.16.1", - "fs-extra": "^9.0.1", - "fuzzaldrin": "^2.1.0", - "glob": "^7.1.4", - "inquirer": "^7.3.3", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3", - "micromatch": "4.0.2", - "mkdirp": "^1.0.4", - "node-gettext": "^3.0.0", - "normalize-path": "^3.0.0", - "ora": "^5.1.0", - "papaparse": "^5.3.0", - "pkg-up": "^3.1.0", - "plurals-cldr": "^1.0.4", - "pofile": "^1.1.0", - "pseudolocale": "^1.1.0", - "ramda": "^0.27.1" + "node_modules/@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "dev": true, + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" }, - "bin": { - "lingui": "lingui.js" + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/reporters": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", + "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^7.0.0" }, "engines": { - "node": ">=10.0.0" + "node": ">= 10.14.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "babel-plugin-macros": "2 || 3", - "typescript": "2 || 3 || 4" + "optionalDependencies": { + "node-notifier": "^8.0.0" } }, - "node_modules/@lingui/cli/node_modules/ansi-styles": { + "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -3464,15 +4974,12 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@lingui/cli/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -3480,12 +4987,9 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lingui/cli/node_modules/color-convert": { + "node_modules/@jest/reporters/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -3497,13 +5001,13 @@ "node": ">=7.0.0" } }, - "node_modules/@lingui/cli/node_modules/color-name": { + "node_modules/@jest/reporters/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@lingui/cli/node_modules/has-flag": { + "node_modules/@jest/reporters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -3512,619 +5016,622 @@ "node": ">=8" } }, - "node_modules/@lingui/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" } }, - "node_modules/@lingui/conf": { - "version": "3.10.4", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.10.4.tgz", - "integrity": "sha512-ywAYoALnGa8GLuRaC6jW2FKkkPF6TQBUs2ACth8lKHjvJNz/Q3ZYKkK+hQj4iLODc5jaCophACrpNrZj1ef/tw==", + "node_modules/@jest/reporters/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "jest-validate": "^26.5.2", - "lodash.get": "^4.4.2" - }, "engines": { - "node": ">=10.0.0" + "node": ">=0.10.0" } }, - "node_modules/@lingui/conf/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@lingui/conf/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "devOptional": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lingui/conf/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jest/source-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 10.14.2" } }, - "node_modules/@lingui/conf/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@lingui/conf/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@jest/source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/@lingui/conf/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 10.14.2" } }, - "node_modules/@lingui/core": { - "version": "3.10.4", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz", - "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==", + "node_modules/@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" }, "engines": { - "node": ">=10.0.0" + "node": ">= 10.14.2" } }, - "node_modules/@lingui/loader": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.10.2.tgz", - "integrity": "sha512-6iktLXTnZktm5V1ZaJqh7LQxQuPf2f/SkxzLrIS8eIRkMYX5Pf+XbUQH1b9aZisZp2gToHLKDpHx+w5PbfH0Ug==", + "node_modules/@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", "dev": true, "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.10.2", - "@lingui/conf": "^3.10.2", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" }, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "node": ">= 10.14.2" } }, - "node_modules/@lingui/macro": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.10.2.tgz", - "integrity": "sha512-nEYUj1F4TUK4aHVpXcAagg7zx41Z7e3wjI1OFlmXkKSupDggYCn6jgzyyx46pUEB8Y5pIzCESPzQIa1P2sTPJA==", + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.10.2", - "ramda": "^0.27.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "babel-plugin-macros": "2 || 3" + "node": ">=8" } }, - "node_modules/@lingui/react": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/react/-/react-3.10.2.tgz", - "integrity": "sha512-tjwSw/7JfbIjZ6G3viE1hM8YC85Yx+NMdEAlSzpwVfxJR66a437//rFVy4U7CmULW2ikNgUBZ47YMYaC6hkMrw==", + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/core": "^3.10.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" + "node": ">=10" } }, - "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", - "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "engines": { + "node": ">=8" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "node_modules/@jest/transform/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "dependencies": { - "remove-trailing-separator": "^1.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, - "optional": true, "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.14.2" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, "dependencies": { - "is-extendable": "^0.1.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, - "optional": true, "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/fill-range/node_modules/extend-shallow": { + "node_modules/@jest/types/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "dependencies": { - "is-extendable": "^0.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, "dependencies": { - "is-extglob": "^2.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, + "node_modules/@josephg/resolvable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", + "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "devOptional": true, "dependencies": { - "kind-of": "^3.0.2" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, - "optional": true, "dependencies": { - "is-buffer": "^1.1.5" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nicolo-ribaudo/chokidar-2/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "node_modules/@juggle/resize-observer": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", + "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@lingui/babel-plugin-extract-messages": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-5.9.3.tgz", + "integrity": "sha512-zm6QHDILmhj8olgLL2zHQn18yFA5mf4hX7QzCr1OOI/e815I0IkecCYue1Ych+y+B+V0eLriiW8AcfpDRCQFFw==", "dev": true, - "optional": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@lingui/babel-plugin-lingui-macro": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-5.9.3.tgz", + "integrity": "sha512-fLMhBarRsuqBGOq2YuEoqOjEAV2VNezz/+f/Dn0vLFHF/kAjnFwTHb8pL8DRSIMsWG16mPrGnLpdROZBmJlFtA==", + "devOptional": true, + "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "@babel/core": "^7.20.12", + "@babel/runtime": "^7.20.13", + "@babel/types": "^7.20.7", + "@lingui/conf": "5.9.3", + "@lingui/core": "5.9.3", + "@lingui/message-utils": "5.9.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "node_modules/@lingui/cli": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-5.9.3.tgz", + "integrity": "sha512-KEE0J4eGlfpiLZ+l019qjraWfqfh5mUmQSJeTFw5PulO4v50zvxw5tDX8stpBzJ3QtgUQZlrMUh0OTGdURaAMg==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" + "@babel/core": "^7.21.0", + "@babel/generator": "^7.21.1", + "@babel/parser": "^7.22.0", + "@babel/runtime": "^7.21.0", + "@babel/types": "^7.21.2", + "@lingui/babel-plugin-extract-messages": "5.9.3", + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "@lingui/conf": "5.9.3", + "@lingui/core": "5.9.3", + "@lingui/format-po": "5.9.3", + "@lingui/message-utils": "5.9.3", + "chokidar": "3.5.1", + "cli-table": "^0.3.11", + "commander": "^10.0.0", + "convert-source-map": "^2.0.0", + "date-fns": "^3.6.0", + "esbuild": "^0.25.1", + "glob": "^11.0.0", + "micromatch": "^4.0.7", + "ms": "^2.1.3", + "normalize-path": "^3.0.0", + "ora": "^5.1.0", + "picocolors": "^1.1.1", + "pofile": "^1.1.4", + "pseudolocale": "^2.0.0", + "source-map": "^0.7.6", + "threads": "^1.7.0" + }, + "bin": { + "lingui": "dist/lingui.js" }, "engines": { - "node": ">= 8" + "node": ">=20.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "node_modules/@lingui/cli/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "engines": { - "node": ">= 8" + "node": "18 || 20 || >=22" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "node_modules/@lingui/cli/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@popperjs/core": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz", - "integrity": "sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "node": "18 || 20 || >=22" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "dev": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "node_modules/@lingui/cli/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "node_modules/@lingui/cli/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", "dev": true, "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "dev": true - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "dev": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "dev": true - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "dev": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "dev": true - }, - "node_modules/@reach/alert": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/alert/-/alert-0.13.2.tgz", - "integrity": "sha512-LDz83AXCrClyq/MWe+0vaZfHp1Ytqn+kgL5VxG7rirUvmluWaj/snxzfNPWn0Ma4K2YENmXXRC/iHt5X95SqIg==", - "dependencies": { - "@reach/utils": "0.13.2", - "@reach/visually-hidden": "0.13.2", - "prop-types": "^15.7.2", - "tslib": "^2.1.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/alert/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/@reach/utils": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.13.2.tgz", - "integrity": "sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==", - "dependencies": { - "@types/warning": "^3.0.0", - "tslib": "^2.1.0", - "warning": "^4.0.3" + "bin": { + "glob": "dist/esm/bin.mjs" }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/utils/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/@reach/visually-hidden": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.13.2.tgz", - "integrity": "sha512-sPZwNS0/duOuG0mYwE5DmgEAzW9VhgU3aIt1+mrfT/xiT9Cdncqke+kRBQgU708q/Ttm9tWsoHni03nn/SuPTQ==", - "dependencies": { - "prop-types": "^15.7.2", - "tslib": "^2.1.0" + "engines": { + "node": "20 || >=22" }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@reach/visually-hidden/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "node_modules/@lingui/cli/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, "engines": { - "node": ">=6" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "node_modules/@lingui/cli/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } + "license": "MIT" }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "node_modules/@lingui/cli/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" } }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, + "node_modules/@lingui/conf": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-5.9.3.tgz", + "integrity": "sha512-hVEoYHmO2A3XmFX4A5RuBgcoVBoM7Xgoqejeq25XELvesJj2s2T15F47TA5n3/S7iTqngd6n/8KxBli9TYwgqQ==", + "devOptional": true, + "license": "MIT", "dependencies": { - "defer-to-connect": "^1.0.1" + "@babel/runtime": "^7.20.13", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^2.5.1", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=6" + "node": ">=20.0.0" } }, - "node_modules/@testing-library/dom": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.26.5.tgz", - "integrity": "sha512-2v/fv0s4keQjJIcD4bjfJMFtvxz5icartxUWdIZVNJR539WD9oxVrvIAPw+3Ydg4RLgxt0rvQx3L9cAjCci0Kg==", - "dev": true, + "node_modules/@lingui/conf/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.10.3", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.1", - "lz-string": "^1.4.4", - "pretty-format": "^26.4.2" + "@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" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "node_modules/@lingui/conf/node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@lingui/conf/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, + "node_modules/@lingui/conf/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true, + "license": "Python-2.0" + }, + "node_modules/@lingui/conf/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/color-convert": { + "node_modules/@lingui/conf/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4132,2598 +5639,2904 @@ "node": ">=7.0.0" } }, - "node_modules/@testing-library/dom/node_modules/color-name": { + "node_modules/@lingui/conf/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true, + "license": "MIT" + }, + "node_modules/@lingui/conf/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, - "node_modules/@testing-library/dom/node_modules/has-flag": { + "node_modules/@lingui/conf/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/@lingui/conf/node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@lingui/conf/node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@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" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@testing-library/jest-dom": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.5.tgz", - "integrity": "sha512-XI+ClHR864i6p2kRCEyhvpVejuer+ObVUF4cjCvRSF88eOMIfqw7RoS9+qoRhyigGswMfT64L6Nt0Ufotxbwtg==", - "dev": true, + "node_modules/@lingui/conf/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "devOptional": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^4.2.2", - "chalk": "^3.0.0", - "css": "^3.0.0", - "css.escape": "^1.5.1", - "lodash": "^4.17.15", - "redent": "^3.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/@lingui/conf/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "devOptional": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, + "node_modules/@lingui/conf/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@lingui/conf/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@lingui/conf/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/@lingui/core": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-5.9.3.tgz", + "integrity": "sha512-3b8LnDjx8POdQ6q6UKBe2DHynyQFCO66vm8/UPQnvlQowUk4Xdu5bK6oet11D9/vrSznrDDS+Qb5JVcNBUImgg==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.9.3" }, "engines": { - "node": ">=7.0.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "@lingui/babel-plugin-lingui-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@lingui/format-po": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-5.9.3.tgz", + "integrity": "sha512-+LMnhWl7EmXrdOv10gopE1g8w8vtPY5Fxk72OORrGQFVMGBIioz4BEnKrNdV1ek2M+GxoMZtnUs17KrJN5Jv9A==", "dev": true, + "license": "MIT", + "dependencies": { + "@lingui/conf": "5.9.3", + "@lingui/message-utils": "5.9.3", + "date-fns": "^3.6.0", + "pofile": "^1.1.4" + }, "engines": { - "node": ">=8" + "node": ">=20.0.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@lingui/loader": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-5.9.3.tgz", + "integrity": "sha512-V+m8vfZ1doPSc26fPZa1zVso75nl70mgdz51OHGAvFfafyDqYasFHmBf5xihS58vy6LXuep3K19Ymf6TG6i5TQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@babel/runtime": "^7.20.13", + "@lingui/cli": "5.9.3", + "@lingui/conf": "5.9.3" }, "engines": { - "node": ">=8" + "node": ">=20.0.0" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/@testing-library/react": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.1.1.tgz", - "integrity": "sha512-DT/P2opE9o4NWCd/oIL73b6VF/Xk9AY8iYSstKfz9cXw0XYPQ5IhA/cuYfoN9nU+mAynW8DpAVfEWdM6e7zF6g==", + "node_modules/@lingui/macro": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-5.9.3.tgz", + "integrity": "sha512-xWqJ+hpp5T+xmE++VYlcfMxrF48LpY5Ak9N/luibY9d0AqvyYZiiv7Xaq7E2eK69v9CJWx+6eXA6uPhC8gHY+Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.1", - "@testing-library/dom": "^7.26.4" + "@lingui/core": "5.9.3", + "@lingui/react": "5.9.3" }, "engines": { - "node": ">=10" + "node": ">=20.0.0" + }, + "peerDependencies": { + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "@lingui/babel-plugin-lingui-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/@testing-library/react-hooks": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.0.tgz", - "integrity": "sha512-c/wvcz/Set+KOvbi07EQO7tujsUIp5HnNAygJoSpMTVkIDcp7JtSemhjRDg1WL6Qsw076inWobTKCepK3mgi8A==", - "dev": true, + "node_modules/@lingui/message-utils": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.9.3.tgz", + "integrity": "sha512-oAK7HA7lcQrzaEaM6G1T5RwwxJxaSKfG/IFIxpZIl49TSFQv+s9YPNgHnVi+d4DmterpXNxy9ZZ+NtckJx6u7g==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/react": ">=16.9.0", - "@types/react-dom": ">=16.9.0", - "@types/react-test-renderer": ">=16.9.0", - "filter-console": "^0.1.1", - "react-error-boundary": "^3.1.0" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@testing-library/user-event": { - "version": "13.1.9", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.9.tgz", - "integrity": "sha512-NZr0zL2TMOs2qk+dNlqrAdbaRW5dAmYwd1yuQ4r7HpkVEOj0MWuUjDWwKhcLd/atdBy8ZSMHSKp+kXSQe47ezg==", - "dev": true, + "node_modules/@lingui/react": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-5.9.3.tgz", + "integrity": "sha512-aje78l3zGGZ3C75fiGhDVKyVALHfiKlYFjcOlZpgXY/JAVfFuZX+6wUGG9x1A8k7BfxrDy/ofHIBahPvNAqoKw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5" + "@babel/runtime": "^7.20.13", + "@lingui/core": "5.9.3" }, "engines": { - "node": ">=10", - "npm": ">=6" + "node": ">=20.0.0" }, "peerDependencies": { - "@testing-library/dom": ">=7.21.4" + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "babel-plugin-macros": "2 || 3", + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@lingui/babel-plugin-lingui-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/@types/accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "dev": true, + "node_modules/@messageformat/parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz", + "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==", "dependencies": { - "@types/node": "*" + "moo": "^0.5.1" } }, - "node_modules/@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true - }, - "node_modules/@types/aria-query": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", - "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", - "dev": true + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true }, - "node_modules/@types/babel__core": { - "version": "7.1.14", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", - "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "dependencies": { - "@babel/types": "^7.0.0" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "engines": { + "node": ">= 8" } }, - "node_modules/@types/babel__traverse": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", - "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@noble/hashes": "^1.1.5" } }, - "node_modules/@types/connect": { - "version": "3.4.34", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", - "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", - "dev": true, - "dependencies": { - "@types/node": "*" + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" } }, - "node_modules/@types/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg==", + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "dev": true }, - "node_modules/@types/cookies": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.6.tgz", - "integrity": "sha512-FK4U5Qyn7/Sc5ih233OuHO0qAkOpEcD/eG6584yEiLKizTFRny86qHLe/rej3HFQrkBuUjF4whFliAdODbVN/w==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/express": "*", - "@types/keygrip": "*", - "@types/node": "*" - } + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true }, - "node_modules/@types/cors": { - "version": "2.8.10", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", - "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "dev": true }, - "node_modules/@types/d3-color": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", - "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==" + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true }, - "node_modules/@types/d3-interpolate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", - "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, "dependencies": { - "@types/d3-color": "^1" + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "node_modules/@types/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true }, - "node_modules/@types/d3-random": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz", - "integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA==" + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true }, - "node_modules/@types/d3-scale": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", - "integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==", - "dependencies": { - "@types/d3-time": "^2" - } + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true }, - "node_modules/@types/d3-shape": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", - "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", - "dependencies": { - "@types/d3-path": "^1" - } + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true }, - "node_modules/@types/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg==" + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true }, - "node_modules/@types/eslint": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.4.tgz", - "integrity": "sha512-YCY4kzHMsHoyKspQH+nwSe+70Kep7Vjt2X+dZe5Vs2vkRudqtoFoUIv1RlJmZB8Hbp7McneupoZij4PadxsK5Q==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "dev": true, "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "type-detect": "4.0.8" } }, - "node_modules/@types/estree": { - "version": "0.0.45", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", - "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", "dev": true, "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "node_modules/@testing-library/dom": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.13.0.tgz", + "integrity": "sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==", "dev": true, "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/fs-capacitor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz", - "integrity": "sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ==", + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@types/node": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "@types/node": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==", - "dev": true - }, - "node_modules/@types/http-assert": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", - "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==", - "dev": true - }, - "node_modules/@types/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" + "engines": { + "node": ">=8" } }, - "node_modules/@types/jest": { - "version": "26.0.15", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz", - "integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==", + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "dependencies": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/@types/keygrip": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", - "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "node_modules/@types/koa": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.1.tgz", - "integrity": "sha512-Qbno7FWom9nNqu0yHZ6A0+RWt4mrYBhw3wpBAQ3+IuzGcLlfeYkzZrnMq5wsxulN2np8M4KKeUpTodsOsSad5Q==", + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@types/accepts": "*", - "@types/content-disposition": "*", - "@types/cookies": "*", - "@types/http-assert": "*", - "@types/http-errors": "*", - "@types/keygrip": "*", - "@types/koa-compose": "*", - "@types/node": "*" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/koa-compose": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz", - "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", + "node_modules/@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", "dev": true, "dependencies": { - "@types/koa": "*" + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@types/lodash": { - "version": "4.14.171", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", - "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==" - }, - "node_modules/@types/lodash.mergewith": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz", - "integrity": "sha512-RY/8IaVENjG19rxTZu9Nukqh0W2UrYgmBj5sdns4hWRZaV8PqR7wIKHFKzvOTjo4zVRV7sVI+yFhAJql12Kfqg==", + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "@types/lodash": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/node": { - "version": "14.0.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz", - "integrity": "sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA==", - "dev": true + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@types/parse-json": { + "node_modules/@testing-library/jest-dom/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/@types/prettier": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", - "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", - "dev": true + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" - }, - "node_modules/@types/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/react": { - "version": "17.0.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz", - "integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==", + "node_modules/@testing-library/react": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "dev": true, "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" } }, - "node_modules/@types/react-dom": { - "version": "16.9.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", - "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "node_modules/@testing-library/react-hooks": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz", + "integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==", + "dev": true, "dependencies": { - "@types/react": "*" + "@babel/runtime": "^7.12.5", + "@types/react": ">=16.9.0", + "@types/react-dom": ">=16.9.0", + "@types/react-test-renderer": ">=16.9.0", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0", + "react-test-renderer": ">=16.9.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } } }, - "node_modules/@types/react-test-renderer": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.0.tgz", - "integrity": "sha512-nvw+F81OmyzpyIE1S0xWpLonLUZCMewslPuA8BtjSKc5XEbn8zEQBXS7KuOLHTNnSOEM2Pum50gHOoZ62tqTRg==", + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", "dev": true, "dependencies": { - "@types/react": "*" + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "engines": { + "node": ">= 6" + } }, - "node_modules/@types/serve-static": { - "version": "1.13.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", - "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "node_modules/@types/accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", "dev": true, "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, - "node_modules/@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "node_modules/@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, - "node_modules/@types/tapable": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", - "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==", + "node_modules/@types/aria-query": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", + "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", "dev": true }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.9.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", - "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", + "node_modules/@types/babel__core": { + "version": "7.1.14", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", + "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", "dev": true, "dependencies": { - "@types/jest": "*" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@types/tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==" - }, - "node_modules/@types/uglify-js": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", - "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", + "node_modules/@types/babel__generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", "dev": true, "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/@types/uglify-js/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "@babel/types": "^7.0.0" } }, - "node_modules/@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" - }, - "node_modules/@types/webpack": { - "version": "4.41.21", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", - "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", + "node_modules/@types/babel__template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", + "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", "dev": true, "dependencies": { - "@types/anymatch": "*", - "@types/node": "*", - "@types/tapable": "*", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "source-map": "^0.6.0" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@types/webpack-sources": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.1.tgz", - "integrity": "sha512-B/RJcbpMp1/od7KADJlW/jeXTEau6NYmhWo+hB29cEfRriYK9SRlH8sY4hI9Au7nrP95Z5MumGvIEiEBHMxoWA==", + "node_modules/@types/babel__traverse": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", + "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", "dev": true, "dependencies": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" + "@babel/types": "^7.3.0" } }, - "node_modules/@types/webpack-sources/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", "dev": true, - "engines": { - "node": ">= 8" + "dependencies": { + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@types/webpack/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@types/ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==", + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "dev": true, "dependencies": { "@types/node": "*" } }, - "node_modules/@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@types/express-serve-static-core": "*", + "@types/node": "*" } }, - "node_modules/@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, - "node_modules/@types/zen-observable": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz", - "integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg==" + "node_modules/@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==" }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.5.0.tgz", - "integrity": "sha512-bW9IpSAKYvkqDGRZzayBXIgPsj2xmmVHLJ+flGSoN0fF98pGoKFhbunIol0VF2Crka7z984EEhFi623Rl7e6gg==", - "dev": true, + "node_modules/@types/d3-color": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.5.tgz", + "integrity": "sha512-5sNP3DmtSnSozxcjqmzQKsDOuVJXZkceo1KJScDc1982kk/TS9mTPc6lpli1gTu1MIBF1YWutpHpjucNWcIj5g==" + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "dependencies": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.5.0", - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/typescript-estree": "4.5.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "@types/geojson": "*" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.5.0.tgz", - "integrity": "sha512-C0cEO0cTMPJ/w4RA/KVe4LFFkkSh9VHoFzKmyaaDWAnPYIEzVCtJ+Un8GZoJhcvq+mPFXEsXa01lcZDHDG6Www==", - "dev": true, + "node_modules/@types/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" + }, + "node_modules/@types/d3-random": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.3.tgz", + "integrity": "sha512-Ghs4R3CcgJ3o6svszRzIH4b8PPYex/COo+rhhZjDAs+bVducXwjmVSi27WcDOaLLCBV2t3tfVH9bYXAL76IvQA==" + }, + "node_modules/@types/d3-shape": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", + "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", "dependencies": { - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/visitor-keys": "4.5.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "@types/d3-path": "^1" } }, - "node_modules/@typescript-eslint/types": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.5.0.tgz", - "integrity": "sha512-n2uQoXnyWNk0Les9MtF0gCK3JiWd987JQi97dMSxBOzVoLZXCNtxFckVqt1h8xuI1ix01t+iMY4h4rFMj/303g==", + "node_modules/@types/d3-time": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.4.tgz", + "integrity": "sha512-BTfLsxTeo7yFxI/haOOf1ZwJ6xKgQLT9dCp+EcmQv87Gox6X+oKl4mLKfO6fnWm3P22+A6DknMNEZany8ql2Rw==" + }, + "node_modules/@types/d3-time-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz", + "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.5.0.tgz", - "integrity": "sha512-gN1mffq3zwRAjlYWzb5DanarOPdajQwx5MEWkWCk0XvqC8JpafDTeioDoow2L4CA/RkYZu7xEsGZRhqrTsAG8w==", + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/visitor-keys": "4.5.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "node_modules/@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "node_modules/@types/express-serve-static-core": { + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "node_modules/@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.5.0.tgz", - "integrity": "sha512-UHq4FSa55NDZqscRU//O5ROFhHa9Hqn9KWTEvJGTArtTQp5GKv9Zqf6d/Q3YXXcFv4woyBml7fJQlQ+OuqRcHA==", + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.5.0", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "@types/node": "*" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true, - "engines": { - "node": ">=10" - } + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true }, - "node_modules/@visx/axis": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.17.1.tgz", - "integrity": "sha512-3JdAY8xwA4xVnzkbXdIzCOWYCknCgw3L185lOJTXWNGO7kIgzbQ2YrLXnet37BFgD83MfxmlP6LhiHLkKVI6OQ==", + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/group": "1.17.1", - "@visx/point": "1.7.0", - "@visx/scale": "1.14.0", - "@visx/shape": "1.17.1", - "@visx/text": "1.17.1", - "classnames": "^2.3.1", - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": "^16.3.0-0" + "@types/node": "*" } }, - "node_modules/@visx/bounds": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz", - "integrity": "sha512-ajF6PTgDoZTfwv5J0ZTx1miXY8lk3sGhMVqE3UsMubdTZBlOgeZMT4OmtTPtbCJTBTgw0FD0gd7X3gZ+3X9HgQ==", - "dependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0", - "react-dom": "^15.0.0-0 || ^16.0.0-0" - } + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "devOptional": true }, - "node_modules/@visx/curve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-1.7.0.tgz", - "integrity": "sha512-n0/SHM4YXjke+aEinhHFZPLMxWu3jbqtvqzfGJyibX8OmbDjavk9P+MHfGokUcw0xHy6Ch3YTuwbYuvVw5ny9A==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "devOptional": true, "dependencies": { - "@types/d3-shape": "^1.3.1", - "d3-shape": "^1.0.6" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@visx/curve/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "devOptional": true, "dependencies": { - "d3-path": "1" + "@types/istanbul-lib-report": "*" } }, - "node_modules/@visx/event": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/event/-/event-1.7.0.tgz", - "integrity": "sha512-RbAoKxvy+ildX2dVXC9/ZX94lQXPwjKgtO9jy7COc15knG4zmzsMCDYDC3uLd0+jE2o/+gSaZ/9r52p6zG5+IQ==", + "node_modules/@types/jest": { + "version": "26.0.15", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz", + "integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/point": "1.7.0" + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" } }, - "node_modules/@visx/grid": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-1.17.1.tgz", - "integrity": "sha512-dse9q3weDqPNmeXK0lGKKPRgGiDuUjJ7Mt7NNonPUyXPctNmv6lJEWZu9HJrXEGiCAVNa8PHJ7Qkns/z+mH88Q==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.0.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz", + "integrity": "sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA==", + "devOptional": true + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/curve": "1.7.0", - "@visx/group": "1.17.1", - "@visx/point": "1.7.0", - "@visx/scale": "1.14.0", - "@visx/shape": "1.17.1", - "classnames": "^2.3.1", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" + "@types/node": "*" } }, - "node_modules/@visx/group": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/group/-/group-1.17.1.tgz", - "integrity": "sha512-g8pSqy8TXAisiOzypnVycDynEGlBhfxtVlwDmsbYB+XSFGEjnOheQSDohDI+ia7ek54Mw9uYe05tx5kP1hRMYw==", + "node_modules/@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prettier": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", + "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "17.0.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", + "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", "dependencies": { - "@types/react": "*", - "classnames": "^2.3.1", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" } }, - "node_modules/@visx/legend": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/legend/-/legend-1.17.1.tgz", - "integrity": "sha512-vdB3EPHXYwTyS4g2MB/sVAvq6ddeyzjlTjsM6YNc6Nagwb4uTOuMeKT+t0jgLJejNMOcY7Q8eBjDDWAdiPNV1A==", + "node_modules/@types/react-dom": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "dependencies": { - "@types/react": "*", - "@visx/group": "1.17.1", - "@visx/scale": "1.14.0", - "classnames": "^2.3.1", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^16.3.0-0" + "@types/react": "*" } }, - "node_modules/@visx/mock-data": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-1.7.0.tgz", - "integrity": "sha512-v7DGro/4q382ReSWmPCKdRR1AtNUp9ERM129omMaTcf52i8RbSYNfMdxucMaEBIbATI905vYqQKA8S6xopPupQ==", + "node_modules/@types/react-test-renderer": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.0.tgz", + "integrity": "sha512-nvw+F81OmyzpyIE1S0xWpLonLUZCMewslPuA8BtjSKc5XEbn8zEQBXS7KuOLHTNnSOEM2Pum50gHOoZ62tqTRg==", + "dev": true, "dependencies": { - "@types/d3-random": "^2.2.0", - "d3-random": "^2.2.2" + "@types/react": "*" } }, - "node_modules/@visx/point": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.7.0.tgz", - "integrity": "sha512-oaoY/HXYHhmpkkeKI4rBPmFtjHWtxSrIhZCVm1ipPoyQp3voJ8L6JD5eUIVmmaUCdUGUGwL1lFLnJiQ2p1Vlwg==" + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true }, - "node_modules/@visx/responsive": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-1.10.1.tgz", - "integrity": "sha512-7FT2BBmWFkFFqynI9C1NYfVOKT1FsNOm6MwWMqXKA7TMomdBW0wdtQNB1bHvwJvWurM/sNqxcQ/CBED6t9xujQ==", - "dependencies": { - "@types/lodash": "^4.14.146", - "@types/react": "*", - "lodash": "^4.17.10", - "prop-types": "^15.6.1", - "resize-observer-polyfill": "1.5.1" - }, - "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" - } + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, - "node_modules/@visx/scale": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-1.14.0.tgz", - "integrity": "sha512-ovbtEOF/d76uGMJ5UZlxdS3t2T8I6md+aIwOXBaq0HdjaCLbe7HLlMyHJKjak/sqBxLAiCGVnechTUpSkfgSQw==", - "dependencies": { - "@types/d3-interpolate": "^1.3.1", - "@types/d3-scale": "^3.3.0", - "@types/d3-time": "^2.0.0", - "d3-interpolate": "^1.4.0", - "d3-scale": "^3.3.0", - "d3-time": "^2.1.1" - } + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true }, - "node_modules/@visx/scale/node_modules/d3-interpolate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", - "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, "dependencies": { - "d3-color": "1" + "@types/express": "*" } }, - "node_modules/@visx/shape": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.17.1.tgz", - "integrity": "sha512-rVYFpytPCnV4s5U0za+jQ2jqFzKnmB3c8RP6fuOfF6kKosFPJcOYg9ikvewojARyMBTr1u3XvWV960Da+xyUdQ==", + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, "dependencies": { - "@types/d3-path": "^1.0.8", - "@types/d3-shape": "^1.3.1", - "@types/lodash": "^4.14.146", - "@types/react": "*", - "@visx/curve": "1.7.0", - "@visx/group": "1.17.1", - "@visx/scale": "1.14.0", - "classnames": "^2.3.1", - "d3-path": "^1.0.5", - "d3-shape": "^1.2.0", - "lodash": "^4.17.15", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^16.3.0-0" + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/@visx/shape/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, "dependencies": { - "d3-path": "1" + "@types/node": "*" } }, - "node_modules/@visx/text": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/text/-/text-1.17.1.tgz", - "integrity": "sha512-Cx6iH0kVq3YqCfFj7U6bMiKwa/bz4Z3q0vPdxmnVGcPjGZM1ac/y61KFH263e164LJ5jFaTYpPrrFmbZoy8+Vg==", - "dependencies": { - "@types/lodash": "^4.14.160", - "@types/react": "*", - "classnames": "^2.3.1", - "lodash": "^4.17.20", - "prop-types": "^15.7.2", - "reduce-css-calc": "^1.3.0" - }, - "peerDependencies": { - "react": "^16.3.0-0" - } + "node_modules/@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true }, - "node_modules/@visx/text/node_modules/balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "node_modules/@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true }, - "node_modules/@visx/text/node_modules/reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dependencies": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - } + "node_modules/@types/tapable": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", + "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==", + "dev": true }, - "node_modules/@visx/tooltip": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.17.1.tgz", - "integrity": "sha512-YfRgVtKSLTn3iW8CT5+CfTWhSXGeAp01SaPDThtdaUTx89rKv5wb4oyVgeQ5g2ScRYVC8mYj5RzY/pj3RrezFQ==", + "node_modules/@types/testing-library__jest-dom": { + "version": "5.9.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", + "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/bounds": "1.7.0", - "classnames": "^2.3.1", - "prop-types": "^15.5.10", - "react-use-measure": "^2.0.4" - }, - "peerDependencies": { - "react": "^16.8.0-0", - "react-dom": "^16.8.0-0" + "@types/jest": "*" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "node_modules/@types/uglify-js": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", "dev": true, "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "source-map": "^0.6.1" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true + "node_modules/@types/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "node_modules/@types/webpack": { + "version": "4.41.21", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", + "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", "dev": true, "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" } }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "node_modules/@types/webpack-sources": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.1.tgz", + "integrity": "sha512-B/RJcbpMp1/od7KADJlW/jeXTEau6NYmhWo+hB29cEfRriYK9SRlH8sY4hI9Au7nrP95Z5MumGvIEiEBHMxoWA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0" + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true + "node_modules/@types/webpack-sources/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "node_modules/@types/webpack/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "@types/node": "*" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "node_modules/@types/yargs": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", "dev": true, "dependencies": { - "@xtuc/long": "4.2.2" + "@types/yargs-parser": "*" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "dev": true + "node_modules/@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "devOptional": true }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { - "envinfo": "^7.7.3" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", - "dev": true - }, - "node_modules/@wry/context": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.0.tgz", - "integrity": "sha512-sAgendOXR8dM7stJw3FusRxFHF/ZinU0lffsA2YTyyIOfic86JX02qlPqPVqJNZJPAxFt+2EE8bvq6ZlS0Kf+Q==", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, "dependencies": { - "tslib": "^2.1.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@wry/context/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/@wry/equality": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.4.0.tgz", - "integrity": "sha512-DxN/uawWfhRbgYE55zVCPOoe+jvsQ4m7PT1Wlxjyb/LCCLuU1UsucV2BbCxFAX8bjcSueFBbB5Qfj1Zfe8e7Fw==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, "dependencies": { - "tslib": "^2.1.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@wry/equality/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/@wry/trie": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.0.tgz", - "integrity": "sha512-Yw1akIogPhAT6XPYsRHlZZIS0tIGmAl9EYXHi2scf7LPKKqdqmow/Hu4kEqP2cJR3EjaU/9L0ZlAjFf3hFxmug==", + "node_modules/@visx/axis": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-2.17.0.tgz", + "integrity": "sha512-44u0b6NP9Go59+mqK69PdsDl7PYfzNpXtcAM/mTsYipRJoUuGU8OwAbZStb1PUhnJJko0j+EZcLy5u7Hm/xDig==", "dependencies": { - "tslib": "^2.1.0" + "@types/react": "*", + "@visx/group": "2.17.0", + "@visx/point": "2.17.0", + "@visx/scale": "2.17.0", + "@visx/shape": "2.17.0", + "@visx/text": "2.17.0", + "classnames": "^2.3.1", + "prop-types": "^15.6.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/@wry/trie/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "node_modules/@visx/axis/node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "node_modules/@visx/axis/node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" + "@types/d3-time": "*" } }, - "node_modules/acorn": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", - "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "node_modules/@visx/axis/node_modules/@visx/curve": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-2.17.0.tgz", + "integrity": "sha512-8Fw2ZalgYbpeoelLqTOmMs/wD8maSKsKS9rRIwmHZ0O0XxY8iG9oVYbD4CLWzf/uFWCY6+qofk4J1g9BWQSXJQ==", + "dependencies": { + "@types/d3-shape": "^1.3.1", + "d3-shape": "^1.0.6" } }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, + "node_modules/@visx/axis/node_modules/@visx/scale": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-2.17.0.tgz", + "integrity": "sha512-ok0RUOSp9VxZzuwo/1I9nsxZxeAdU6wsvIb+cEyMrCuDwm79wzaioSkafAGSb39cKYNrGobFlA3vUd7+JZPCaw==", "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-time": "^2.0.0", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-time": "^2.1.1" } }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" + "node_modules/@visx/axis/node_modules/@visx/shape": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-2.17.0.tgz", + "integrity": "sha512-c2uun6f9souLIyUx+WLetG2JSJ4hF3dJqs1yoFZuO5BLNcU35LTCbqvEq10hLPB7TLqkA0s3jWt/rpE4M3S0Mw==", + "dependencies": { + "@types/d3-path": "^1.0.8", + "@types/d3-shape": "^1.3.1", + "@types/lodash": "^4.14.172", + "@types/react": "*", + "@visx/curve": "2.17.0", + "@visx/group": "2.17.0", + "@visx/scale": "2.17.0", + "classnames": "^2.3.1", + "d3-path": "^1.0.5", + "d3-shape": "^1.2.0", + "lodash": "^4.17.21", + "prop-types": "^15.5.10" }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "node_modules/@visx/bounds": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-2.17.0.tgz", + "integrity": "sha512-XsoyTAyCm+DZbrPgP3IZFZAcNqBmXFBLSep04TqnrEA3hf16GxIzcpaGe+hAVhPg5yzBdjc7tLk6s0h5F44niA==", "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" + "@types/react": "*", + "@types/react-dom": "*", + "prop-types": "^15.5.10" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0", + "react-dom": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "node_modules/ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, + "node_modules/@visx/curve": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-3.3.0.tgz", + "integrity": "sha512-G1l1rzGWwIs8ka3mBhO/gj8uYK6XdU/3bwRSoiZ+MockMahQFPog0bUkuVgPwwzPSJfsA/E5u53Y/DNesnHQxg==", "dependencies": { - "string-width": "^3.0.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" + "@types/d3-shape": "^1.3.1", + "d3-shape": "^1.0.6" } }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, + "node_modules/@visx/event": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/event/-/event-2.17.0.tgz", + "integrity": "sha512-fg2UWo89RgKgWWnnqI+i7EF8Ry+3CdMHTND4lo4DyJvcZZUCOwhxCHMQ4/PHW0EAUfxI51nGadcE1BcEVR5zWw==", "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" + "@types/react": "*", + "@visx/point": "2.17.0" } }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, + "node_modules/@visx/glyph": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/glyph/-/glyph-3.3.0.tgz", + "integrity": "sha512-U2r1rFLpim3afKuuAmrbxXGSDCaLwXHmjXxWN8PiIQPMxpS7eaa/V5g2TRd/+x0KCkaf3Ismk4VKMl8ZlrmxIQ==", "dependencies": { - "ansi-regex": "^4.1.0" + "@types/d3-shape": "^1.3.1", + "@types/react": "*", + "@visx/group": "3.3.0", + "classnames": "^2.3.1", + "d3-shape": "^1.2.0", + "prop-types": "^15.6.2" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, + "node_modules/@visx/glyph/node_modules/@visx/group": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.3.0.tgz", + "integrity": "sha512-yKepDKwJqlzvnvPS0yDuW13XNrYJE4xzT6xM7J++441nu6IybWWwextyap8ey+kU651cYDb+q1Oi6aHvQwyEyw==", "dependencies": { - "type-fest": "^0.11.0" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@visx/gradient": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/gradient/-/gradient-3.3.0.tgz", + "integrity": "sha512-t3vqukahDQsJ64/fcm85woFm2XPpSPMBz92gFvaY4J8EJY3e6rFOg382v5Dm17fgNsLRKJA0Vqo7mUtDe2pWOw==", "dependencies": { - "color-convert": "^1.9.0" + "@types/react": "*", + "prop-types": "^15.5.7" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, + "node_modules/@visx/grid": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-3.5.0.tgz", + "integrity": "sha512-i1pdobTE223ItMiER3q4ojIaZWja3vg46TkS6FotnBZ4c0VRDHSrALQPdi0na+YEgppASWCQ2WrI/vD6mIkhSg==", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@types/react": "*", + "@visx/curve": "3.3.0", + "@visx/group": "3.3.0", + "@visx/point": "3.3.0", + "@visx/scale": "3.5.0", + "@visx/shape": "3.5.0", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-cache-control": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.14.0.tgz", - "integrity": "sha512-qN4BCq90egQrgNnTRMUHikLZZAprf3gbm8rC5Vwmc6ZdLolQ7bFsa769Hqi6Tq/lS31KLsXBLTOsRbfPHph12w==", - "deprecated": "The functionality provided by the `apollo-cache-control` package is built in to `apollo-server-core` starting with Apollo Server 3. See https://www.apollographql.com/docs/apollo-server/migration/#cachecontrol for details.", - "dev": true, + "node_modules/@visx/grid/node_modules/@visx/group": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.3.0.tgz", + "integrity": "sha512-yKepDKwJqlzvnvPS0yDuW13XNrYJE4xzT6xM7J++441nu6IybWWwextyap8ey+kU651cYDb+q1Oi6aHvQwyEyw==", "dependencies": { - "apollo-server-env": "^3.1.0", - "apollo-server-plugin-base": "^0.13.0" - }, - "engines": { - "node": ">=6.0" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-datasource": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.9.0.tgz", - "integrity": "sha512-y8H99NExU1Sk4TvcaUxTdzfq2SZo6uSj5dyh75XSQvbpH6gdAXIW9MaBcvlNC7n0cVPsidHmOcHOWxJ/pTXGjA==", - "dev": true, - "dependencies": { - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0" - }, - "engines": { - "node": ">=6" - } + "node_modules/@visx/grid/node_modules/@visx/point": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/point/-/point-3.3.0.tgz", + "integrity": "sha512-03eBBIJarkmX79WbeEGTUZwmS5/MUuabbiM9KfkGS9pETBTWkp1DZtEHZdp5z34x5TDQVLSi0rk1Plg3/8RtDg==" }, - "node_modules/apollo-graphql": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.2.tgz", - "integrity": "sha512-+c/vqC2LPq3e5kO7MfBxDDiljzLog/THZr9Pd46HVaKAhHUxFL0rJEbT17VhjdOoZGWFWLYG7x9hiN6EQD1xZQ==", - "dev": true, + "node_modules/@visx/group": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-2.17.0.tgz", + "integrity": "sha512-60Y2dIKRh3cp/Drpq//wM067ZNrnCcvFCXufPgIihv0Ix8O7oMsYxu3ch4XUMjks+U2IAZQr5Dnc+C9sTQFkhw==", "dependencies": { - "core-js-pure": "^3.10.2", - "lodash.sortby": "^4.7.0", - "sha.js": "^2.4.11" - }, - "engines": { - "node": ">=6" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, "peerDependencies": { - "graphql": "^14.2.1 || ^15.0.0" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-link": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz", - "integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==", - "dev": true, + "node_modules/@visx/legend": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/legend/-/legend-2.17.0.tgz", + "integrity": "sha512-GslbNTjGXSR1Oxckqj4Pf0iHpPX7m3paGihTkeM+4fbL0xXEQ1GwzpHAXQjgVnbEMpy9J4tJi4WV/SnUuGhD4Q==", "dependencies": { - "apollo-utilities": "^1.3.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.9.3", - "zen-observable-ts": "^0.8.21" + "@types/react": "*", + "@visx/group": "2.17.0", + "@visx/scale": "2.17.0", + "classnames": "^2.3.1", + "prop-types": "^15.5.10" }, "peerDependencies": { - "graphql": "^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-link/node_modules/ts-invariant": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", - "integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", - "dev": true, + "node_modules/@visx/legend/node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "dependencies": { - "tslib": "^1.9.3" + "@types/d3-color": "*" } }, - "node_modules/apollo-reporting-protobuf": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.8.0.tgz", - "integrity": "sha512-B3XmnkH6Y458iV6OsA7AhfwvTgeZnFq9nPVjbxmLKnvfkEl8hYADtz724uPa0WeBiD7DSFcnLtqg9yGmCkBohg==", - "dev": true, + "node_modules/@visx/legend/node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "dependencies": { - "@apollo/protobufjs": "1.2.2" + "@types/d3-time": "*" } }, - "node_modules/apollo-server": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.24.0.tgz", - "integrity": "sha512-KYYyBRLvqJaXFk64HfdgPilm8bqc2ON3hwhWmWx2Apsy4PC7paVK4gYFAUh9piPpeVZfF3t7zm+2J+9R47otjA==", - "dev": true, + "node_modules/@visx/legend/node_modules/@visx/scale": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-2.17.0.tgz", + "integrity": "sha512-ok0RUOSp9VxZzuwo/1I9nsxZxeAdU6wsvIb+cEyMrCuDwm79wzaioSkafAGSb39cKYNrGobFlA3vUd7+JZPCaw==", "dependencies": { - "apollo-server-core": "^2.24.0", - "apollo-server-express": "^2.24.0", - "express": "^4.0.0", - "graphql-subscriptions": "^1.0.0", - "graphql-tools": "^4.0.8", - "stoppable": "^1.1.0" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-time": "^2.0.0", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-time": "^2.1.1" } }, - "node_modules/apollo-server-caching": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.7.0.tgz", - "integrity": "sha512-MsVCuf/2FxuTFVhGLK13B+TZH9tBd2qkyoXKKILIiGcZ5CDUEBO14vIV63aNkMkS1xxvK2U4wBcuuNj/VH2Mkw==", - "dev": true, + "node_modules/@visx/mock-data": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-3.3.0.tgz", + "integrity": "sha512-yb5R/tAU8fjwRSc5VL1UPYbkD+BoYjXUorblE3/oDcSfFrOvpRMZzSaYCBbZ6jtllge3Ks6QVzwyUUj1/xweqQ==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=6" + "@types/d3-random": "^2.2.0", + "d3-random": "^2.2.2" } }, - "node_modules/apollo-server-core": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.2.tgz", - "integrity": "sha512-lrohEjde2TmmDTO7FlOs8x5QQbAS0Sd3/t0TaK2TWaodfzi92QAvIsq321Mol6p6oEqmjm8POIDHW1EuJd7XMA==", - "dev": true, - "dependencies": { - "@apollographql/apollo-tools": "^0.5.0", - "@apollographql/graphql-playground-html": "1.6.27", - "@apollographql/graphql-upload-8-fork": "^8.1.3", - "@josephg/resolvable": "^1.0.0", - "@types/ws": "^7.0.0", - "apollo-cache-control": "^0.14.0", - "apollo-datasource": "^0.9.0", - "apollo-graphql": "^0.9.0", - "apollo-reporting-protobuf": "^0.8.0", - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0", - "apollo-server-errors": "^2.5.0", - "apollo-server-plugin-base": "^0.13.0", - "apollo-server-types": "^0.9.0", - "apollo-tracing": "^0.15.0", - "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.0.0", - "graphql-extensions": "^0.15.0", - "graphql-tag": "^2.11.0", - "graphql-tools": "^4.0.8", - "loglevel": "^1.6.7", - "lru-cache": "^6.0.0", - "sha.js": "^2.4.11", - "subscriptions-transport-ws": "^0.9.19", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } + "node_modules/@visx/mock-data/node_modules/d3-random": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", + "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" }, - "node_modules/apollo-server-core/node_modules/graphql-tools": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", - "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", - "dev": true, + "node_modules/@visx/point": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.17.0.tgz", + "integrity": "sha512-fUdGQBLGaSVbFTbQ6k+1nPisbqYjTjAdo9FhlwLd3W3uyXN/39Sx2z3N2579sVNBDzmCKdYNQIU0HC+/3Vqo6w==" + }, + "node_modules/@visx/responsive": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-2.17.0.tgz", + "integrity": "sha512-3dY2shGbQnoknIRv3Vfnwsy3ZA8Q5Q/rYnTLiokWChYRfNC8NMPoX9mprEeb/gMAxtKjaLn3zcCgd8R+eetxIQ==", "dependencies": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" + "@juggle/resize-observer": "^3.3.1", + "@types/lodash": "^4.14.172", + "@types/react": "*", + "lodash": "^4.17.21", + "prop-types": "^15.6.1" }, "peerDependencies": { - "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-server-core/node_modules/graphql-tools/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" + "node_modules/@visx/scale": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-3.5.0.tgz", + "integrity": "sha512-xo3zrXV2IZxrMq9Y9RUVJUpd93h3NO/r/y3GVi5F9AsbOzOhsLIbsPkunhO9mpUSR8LZ9TiumLEBrY+3frRBSg==", + "dependencies": { + "@visx/vendor": "3.5.0" } }, - "node_modules/apollo-server-env": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-3.1.0.tgz", - "integrity": "sha512-iGdZgEOAuVop3vb0F2J3+kaBVi4caMoxefHosxmgzAbbSpvWehB8Y1QiSyyMeouYC38XNVk5wnZl+jdGSsWsIQ==", - "dev": true, + "node_modules/@visx/shape": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-3.5.0.tgz", + "integrity": "sha512-DP3t9jBQ7dSE3e6ptA1xO4QAIGxO55GrY/6P+S6YREuQGjZgq20TLYLAsiaoPEzFSS4tp0m12ZTPivWhU2VBTw==", "dependencies": { - "node-fetch": "^2.6.1", - "util.promisify": "^1.0.0" + "@types/d3-path": "^1.0.8", + "@types/d3-shape": "^1.3.1", + "@types/lodash": "^4.14.172", + "@types/react": "*", + "@visx/curve": "3.3.0", + "@visx/group": "3.3.0", + "@visx/scale": "3.5.0", + "classnames": "^2.3.1", + "d3-path": "^1.0.5", + "d3-shape": "^1.2.0", + "lodash": "^4.17.21", + "prop-types": "^15.5.10" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-server-errors": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz", - "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==", - "dev": true, - "engines": { - "node": ">=6" + "node_modules/@visx/shape/node_modules/@visx/group": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.3.0.tgz", + "integrity": "sha512-yKepDKwJqlzvnvPS0yDuW13XNrYJE4xzT6xM7J++441nu6IybWWwextyap8ey+kU651cYDb+q1Oi6aHvQwyEyw==", + "dependencies": { + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-server-express": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.2.tgz", - "integrity": "sha512-A2gF2e85vvDugPlajbhr0A14cDFDIGX0mteNOJ8P3Z3cIM0D4hwrWxJidI+SzobefDIyIHu1dynFedJVhV0euQ==", - "dev": true, + "node_modules/@visx/text": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.17.0.tgz", + "integrity": "sha512-Eu6b8SMI+LU4O6H4l/QhCa7c4GtDTQO6jhSYuU70pdTST1Bm74nImPGekG2xDW3uxaLlkb8fDpvXag0Z7v+vlQ==", "dependencies": { - "@apollographql/graphql-playground-html": "1.6.27", - "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.0", - "@types/cors": "2.8.10", - "@types/express": "^4.17.12", - "@types/express-serve-static-core": "^4.17.21", - "accepts": "^1.3.5", - "apollo-server-core": "^2.25.2", - "apollo-server-types": "^0.9.0", - "body-parser": "^1.18.3", - "cors": "^2.8.5", - "express": "^4.17.1", - "graphql-subscriptions": "^1.0.0", - "graphql-tools": "^4.0.8", - "parseurl": "^1.3.2", - "subscriptions-transport-ws": "^0.9.19", - "type-is": "^1.6.16" - }, - "engines": { - "node": ">=6" + "@types/lodash": "^4.14.172", + "@types/react": "*", + "classnames": "^2.3.1", + "lodash": "^4.17.21", + "prop-types": "^15.7.2", + "reduce-css-calc": "^1.3.0" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-server-express/node_modules/graphql-tools": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", - "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", - "dev": true, + "node_modules/@visx/tooltip": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-2.17.0.tgz", + "integrity": "sha512-+dMHURP9NqSFZLomMUnoVYjRs+I2qcOw1yYvLtTp/4GUAFRMSUJoSJeuLwng1VBIgCEB95xuQ95NgGID4qzPxA==", "dependencies": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" + "@types/react": "*", + "@visx/bounds": "2.17.0", + "classnames": "^2.3.1", + "prop-types": "^15.5.10", + "react-use-measure": "^2.0.4" }, "peerDependencies": { - "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0" + "react": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0", + "react-dom": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/apollo-server-express/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" + "node_modules/@visx/vendor": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-3.5.0.tgz", + "integrity": "sha512-yt3SEZRVmt36+APsCISSO9eSOtzQkBjt+QRxNRzcTWuzwMAaF3PHCCSe31++kkpgY9yFoF+Gfes1TBe5NlETiQ==", + "dependencies": { + "@types/d3-array": "3.0.3", + "@types/d3-color": "3.1.0", + "@types/d3-delaunay": "6.0.1", + "@types/d3-format": "3.0.1", + "@types/d3-geo": "3.1.0", + "@types/d3-interpolate": "3.0.1", + "@types/d3-scale": "4.0.2", + "@types/d3-time": "3.0.0", + "@types/d3-time-format": "2.1.0", + "d3-array": "3.2.1", + "d3-color": "3.1.0", + "d3-delaunay": "6.0.2", + "d3-format": "3.1.0", + "d3-geo": "3.1.0", + "d3-interpolate": "3.0.1", + "d3-scale": "4.0.2", + "d3-time": "3.1.0", + "d3-time-format": "4.1.0", + "internmap": "2.0.3" + } + }, + "node_modules/@visx/vendor/node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==" + }, + "node_modules/@visx/vendor/node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dependencies": { + "@types/d3-color": "*" } }, - "node_modules/apollo-server-plugin-base": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.13.0.tgz", - "integrity": "sha512-L3TMmq2YE6BU6I4Tmgygmd0W55L+6XfD9137k+cWEBFu50vRY4Re+d+fL5WuPkk5xSPKd/PIaqzidu5V/zz8Kg==", - "dev": true, + "node_modules/@visx/vendor/node_modules/@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", "dependencies": { - "apollo-server-types": "^0.9.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@types/d3-time": "*" } }, - "node_modules/apollo-server-types": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.9.0.tgz", - "integrity": "sha512-qk9tg4Imwpk732JJHBkhW0jzfG0nFsLqK2DY6UhvJf7jLnRePYsPxWfPiNkxni27pLE2tiNlCwoDFSeWqpZyBg==", - "dev": true, + "node_modules/@visx/vendor/node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" + }, + "node_modules/@visx/vendor/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", "dependencies": { - "apollo-reporting-protobuf": "^0.8.0", - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0" + "internmap": "1 - 2" }, "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "node": ">=12" } }, - "node_modules/apollo-server/node_modules/graphql-tools": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", - "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", - "dev": true, + "node_modules/@visx/vendor/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "dependencies": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" + "d3-array": "2 - 3" }, - "peerDependencies": { - "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0" + "engines": { + "node": ">=12" } }, - "node_modules/apollo-server/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, - "bin": { - "uuid": "bin/uuid" + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "node_modules/apollo-tracing": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.15.0.tgz", - "integrity": "sha512-UP0fztFvaZPHDhIB/J+qGuy6hWO4If069MGC98qVs0I8FICIGu4/8ykpX3X3K6RtaQ56EDAWKykCxFv4ScxMeA==", - "deprecated": "The `apollo-tracing` package is no longer part of Apollo Server 3. See https://www.apollographql.com/docs/apollo-server/migration/#tracing for details", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "dependencies": { - "apollo-server-env": "^3.1.0", - "apollo-server-plugin-base": "^0.13.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "node_modules/apollo-utilities": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz", - "integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==", + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "dependencies": { - "@wry/equality": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.10.0" - }, - "peerDependencies": { - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "node_modules/apollo-utilities/node_modules/@wry/equality": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz", - "integrity": "sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "dependencies": { - "tslib": "^1.9.3" + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/apollo-utilities/node_modules/ts-invariant": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", - "integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "dependencies": { - "tslib": "^1.9.3" + "@xtuc/long": "4.2.2" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" + "@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" } }, - "node_modules/aria-hidden": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz", - "integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, "dependencies": { - "tslib": "^1.0.0" - }, - "engines": { - "node": ">=8.5.0" + "@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" } }, - "node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "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" } }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" } }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", "dev": true, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "dev": true, "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" + "envinfo": "^7.7.3" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "webpack-cli": "4.x.x" } }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" + "peerDependencies": { + "webpack-cli": "4.x.x" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, + "node_modules/@wry/context": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz", + "integrity": "sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw==", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/@wry/context/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, - "node_modules/array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, + "node_modules/@wry/equality": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.2.tgz", + "integrity": "sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA==", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "tslib": "^2.3.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", - "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" + "node_modules/@wry/equality/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/@wry/trie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.1.tgz", + "integrity": "sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw==", + "dependencies": { + "tslib": "^2.3.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, + "node_modules/@wry/trie/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@zag-js/dom-query": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", + "integrity": "sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==" + }, + "node_modules/@zag-js/element-size": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.10.5.tgz", + "integrity": "sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==" + }, + "node_modules/@zag-js/focus-visible": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.16.0.tgz", + "integrity": "sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==", "dependencies": { - "safer-buffer": "~2.1.0" + "@zag-js/dom-query": "0.16.0" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=4" + "node": ">=0.4.0" } }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", "dev": true, "dependencies": { - "lodash": "^4.17.14" + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" } }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "node_modules/async-retry": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", - "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, - "dependencies": { - "retry": "0.12.0" + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true, "engines": { - "node": ">= 4.0.0" + "node": ">=0.4.0" } }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, - "bin": { - "atob": "bin/atob.js" + "dependencies": { + "debug": "4" }, "engines": { - "node": ">= 4.5.0" + "node": ">= 6.0.0" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": "*" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">= 10.14.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "type-fest": "^0.11.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">= 8.9" + "node": ">= 8" } }, - "node_modules/babel-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "node_modules/apollo-datasource": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.2.tgz", + "integrity": "sha512-L5TiS8E2Hn/Yz7SSnWIVbZw0ZfEIXZCa5VUiVxD9P53JvSrf4aStvsFDlGWPvpIdCR+aly2CfoB79B9/JjKFqg==", + "deprecated": "The `apollo-datasource` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "minimist": "^1.2.0" + "@apollo/utils.keyvaluecache": "^1.0.1", + "apollo-server-env": "^4.2.1" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=12.0" } }, - "node_modules/babel-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "node_modules/apollo-reporting-protobuf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.4.0.tgz", + "integrity": "sha512-h0u3EbC/9RpihWOmcSsvTW2O6RXVaD/mPEjfrPkxRPTEPWqncsgOoRJw+wih4OqfH3PvTJvoEIf4LwKrUaqWog==", + "deprecated": "The `apollo-reporting-protobuf` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/usage-reporting-protobuf` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" + "@apollo/protobufjs": "1.2.6" } }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "node_modules/apollo-reporting-protobuf/node_modules/@apollo/protobufjs": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.6.tgz", + "integrity": "sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw==", "dev": true, + "hasInstallScript": true, "dependencies": { - "object.assign": "^4.1.0" + "@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/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + }, + "bin": { + "apollo-pbjs": "bin/pbjs", + "apollo-pbts": "bin/pbts" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "node_modules/apollo-reporting-protobuf/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true + }, + "node_modules/apollo-server": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.12.0.tgz", + "integrity": "sha512-wZHLgBoIdGxr/YpPTG5RwNnS+B2y70T/nCegCnU6Yl+H3PXB92OIguLMhdJIZVjukIOhiQT12dNIehqLQ+1hMQ==", + "deprecated": "The `apollo-server` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" + "@types/express": "4.17.14", + "apollo-server-core": "^3.12.0", + "apollo-server-express": "^3.12.0", + "express": "^4.17.1" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" + "node_modules/apollo-server-core": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.12.1.tgz", + "integrity": "sha512-9SF5WAkkV0FZQ2HVUWI9Jada1U0jg7e8NCN9EklbtvaCeUlOLyXyM+KCWuZ7+dqHxjshbtcwylPHutt3uzoNkw==", + "deprecated": "The `apollo-server-core` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", + "dev": true, + "dependencies": { + "@apollo/utils.keyvaluecache": "^1.0.1", + "@apollo/utils.logger": "^1.0.0", + "@apollo/utils.usagereporting": "^1.0.0", + "@apollographql/apollo-tools": "^0.5.3", + "@apollographql/graphql-playground-html": "1.6.29", + "@graphql-tools/mock": "^8.1.2", + "@graphql-tools/schema": "^8.0.0", + "@josephg/resolvable": "^1.0.0", + "apollo-datasource": "^3.3.2", + "apollo-reporting-protobuf": "^3.4.0", + "apollo-server-env": "^4.2.1", + "apollo-server-errors": "^3.3.1", + "apollo-server-plugin-base": "^3.7.2", + "apollo-server-types": "^3.8.0", + "async-retry": "^1.2.1", + "fast-json-stable-stringify": "^2.1.0", + "graphql-tag": "^2.11.0", + "loglevel": "^1.6.8", + "lru-cache": "^6.0.0", + "node-abort-controller": "^3.0.1", + "sha.js": "^2.4.11", + "uuid": "^9.0.0", + "whatwg-mimetype": "^3.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/babel-plugin-macros": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.0.1.tgz", - "integrity": "sha512-CKt4+Oy9k2wiN+hT1uZzOw7d8zb1anbQpf7KLwaaXRCi/4pzKdFKHf7v5mvoPmjkmxshh7eKZQuRop06r5WP4w==", + "node_modules/apollo-server-core/node_modules/@graphql-tools/merge": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.3.1.tgz", + "integrity": "sha512-BMm99mqdNZbEYeTPK3it9r9S6rsZsQKtlqJsSBknAclXq2pGEfOxjcIZi+kBSkHZKPKCRrYDd5vY0+rUmIHVLg==", "dev": true, "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" + "@graphql-tools/utils": "8.9.0", + "tslib": "^2.4.0" }, - "engines": { - "node": ">=10", - "npm": ">=6" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "node_modules/apollo-server-core/node_modules/@graphql-tools/schema": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.5.1.tgz", + "integrity": "sha512-0Esilsh0P/qYcB5DKQpiKeQs/jevzIadNTaT0jeWklPMwNbT7yMX4EqZany7mbeRRlSRwMzNzL5olyFdffHBZg==", "dev": true, "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@graphql-tools/merge": "8.3.1", + "@graphql-tools/utils": "8.9.0", + "tslib": "^2.4.0", + "value-or-promise": "1.0.11" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "node_modules/apollo-server-core/node_modules/@graphql-tools/utils": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.9.0.tgz", + "integrity": "sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" + "tslib": "^2.4.0" }, - "engines": { - "node": ">= 10.14.2" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + "node_modules/apollo-server-core/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "node_modules/apollo-server-core/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "node_modules/apollo-server-core/node_modules/value-or-promise": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", + "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==", "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "node_modules/apollo-server-core/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/apollo-server-env": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz", + "integrity": "sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g==", + "deprecated": "The `apollo-server-env` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/utils.fetcher` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "is-descriptor": "^1.0.0" + "node-fetch": "^2.6.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0" } }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "node_modules/apollo-server-errors": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz", + "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==", + "deprecated": "The `apollo-server-errors` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" + } + }, + "node_modules/apollo-server-express": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.12.0.tgz", + "integrity": "sha512-m8FaGPUfDOEGSm7QRWRmUUGjG/vqvpQoorkId9/FXkC57fz/A59kEdrzkMt9538Xgsa5AV+X4MEWLJhTvlW3LQ==", + "deprecated": "The `apollo-server-express` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "kind-of": "^6.0.0" + "@types/accepts": "^1.3.5", + "@types/body-parser": "1.19.2", + "@types/cors": "2.8.12", + "@types/express": "4.17.14", + "@types/express-serve-static-core": "4.17.31", + "accepts": "^1.3.5", + "apollo-server-core": "^3.12.0", + "apollo-server-types": "^3.8.0", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "parseurl": "^1.3.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0" + }, + "peerDependencies": { + "express": "^4.17.1", + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "node_modules/apollo-server-plugin-base": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.2.tgz", + "integrity": "sha512-wE8dwGDvBOGehSsPTRZ8P/33Jan6/PmL0y0aN/1Z5a5GcbFhDaaJCjK5cav6npbbGL2DPKK0r6MPXi3k3N45aw==", + "deprecated": "The `apollo-server-plugin-base` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "kind-of": "^6.0.0" + "apollo-server-types": "^3.8.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "node_modules/apollo-server-types": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.8.0.tgz", + "integrity": "sha512-ZI/8rTE4ww8BHktsVpb91Sdq7Cb71rdSkXELSwdSR0eXu600/sY+1UXhTWdiJvk+Eq5ljqoHLwLbY2+Clq2b9A==", + "deprecated": "The `apollo-server-types` package is part of Apollo Server v2 and v3, which are now deprecated (end-of-life October 22nd 2023). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.", "dev": true, "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "@apollo/utils.keyvaluecache": "^1.0.1", + "@apollo/utils.logger": "^1.0.0", + "apollo-reporting-protobuf": "^3.4.0", + "apollo-server-env": "^4.2.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "node_modules/bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" + "sprintf-js": "~1.0.2" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", "dependencies": { - "tweetnacl": "^0.14.3" + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "node_modules/aria-hidden/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", "dev": true, "engines": { - "node": "*" + "node": ">=6.0" } }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/bl/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/bonjour/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, - "node_modules/boolbase": { + "node_modules/assign-symbols": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dev": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "dev": true, + "dependencies": { + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 10.14.2" } }, - "node_modules/boxen/node_modules/ansi-styles": { + "node_modules/babel-jest/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -6733,34 +8546,22 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/boxen/node_modules/color-convert": { + "node_modules/babel-jest/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -6772,13 +8573,13 @@ "node": ">=7.0.0" } }, - "node_modules/boxen/node_modules/color-name": { + "node_modules/babel-jest/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/boxen/node_modules/has-flag": { + "node_modules/babel-jest/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -6787,7 +8588,7 @@ "node": ">=8" } }, - "node_modules/boxen/node_modules/supports-color": { + "node_modules/babel-jest/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -6799,320 +8600,657 @@ "node": ">=8" } }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, "engines": { - "node": ">=8" + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "object.assign": "^4.1.0" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", - "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", + "node_modules/babel-plugin-jest-hoist": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", + "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "dev": true, "dependencies": { - "caniuse-lite": "^1.0.30001173", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.634", - "escalade": "^3.1.1", - "node-releases": "^1.1.69" - }, - "bin": { - "browserslist": "cli.js" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 10.14.2" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "dependencies": { - "node-int64": "^0.4.0" + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", + "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", "dev": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/busboy": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", - "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz", + "integrity": "sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==", "dev": true, "dependencies": { - "dicer": "0.3.0" + "@babel/helper-define-polyfill-provider": "^0.3.2" }, - "engines": { - "node": ">=4.5.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "node_modules/babel-preset-jest": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", + "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 10.14.2" } }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "dependencies": { - "collection-visit": "^1.0.0", + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", + "define-property": "^1.0.0", "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "is-descriptor": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "kind-of": "^6.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/call-bind": { + "node_modules/base/node_modules/is-descriptor": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "devOptional": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/camel-case/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "node_modules/camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001246", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz", - "integrity": "sha512-Tc+ff0Co/nFNbLOrziBXmMVtpt9S2c2Y+Z9Nk9Khj09J+0zR9ejvIW5qkZAErCbOrVODCx/MN+GpB5FNBs5GFA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "node_modules/bl": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "dev": true, "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=4" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 0.8" }, - "optionalDependencies": { - "fsevents": "~2.3.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/chokidar/node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "node_modules/body-parser/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/chokidar/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/bonjour-service": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.12.tgz", + "integrity": "sha512-pMmguXYCu63Ug37DluMKEHdxc+aaIf/ay4YbF8Gxtba+9d3u+rmEWy61VK3Z3hp8Rskok3BunHYnG0dUHAsblw==", "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.4" + } + }, + "node_modules/bonjour-service/node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camel-case/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001768", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", + "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "dependencies": { + "rsvp": "^4.8.4" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, "node_modules/chokidar/node_modules/readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -7182,15 +9320,15 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "node_modules/clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", + "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", "dev": true, "dependencies": { "source-map": "~0.6.0" }, "engines": { - "node": ">= 4.0" + "node": ">= 10.0" } }, "node_modules/clean-css/node_modules/source-map": { @@ -7215,18 +9353,6 @@ "node": ">=8.9.0" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -7249,25 +9375,15 @@ } }, "node_modules/cli-table": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.4.tgz", - "integrity": "sha512-1vinpnX/ZERcmE443i3SZTmU5DF0rPO9DrL4I2iVAllhxzCM9SzPlHnz19fsZB78htkKZvYBvj6SZ6vXnaxmTA==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", "dev": true, "dependencies": { - "chalk": "^2.4.1", - "string-width": "^4.2.0" + "colors": "1.0.3" }, "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" + "node": ">= 0.2.0" } }, "node_modules/cliui": { @@ -7304,15 +9420,6 @@ "node": ">=6" } }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7346,6 +9453,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -7353,14 +9461,30 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" }, "node_modules/colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true }, - "node_modules/combined-stream": { + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", @@ -7373,12 +9497,12 @@ } }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { - "node": ">= 6" + "node": ">=14" } }, "node_modules/commondir": { @@ -7397,7 +9521,6 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -7406,36 +9529,53 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/compute-scroll-into-view": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz", - "integrity": "sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -7443,56 +9583,49 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true, "engines": { "node": ">=0.8" } }, - "node_modules/contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -7506,9 +9639,10 @@ } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7519,9 +9653,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "node_modules/cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, "node_modules/copy-descriptor": { @@ -7534,38 +9668,118 @@ } }, "node_modules/copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", "dependencies": { "toggle-selection": "^1.0.6" } }, - "node_modules/core-js-compat": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", - "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", + "node_modules/copy-webpack-plugin": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", + "dev": true, + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "browserslist": "^4.16.1", - "semver": "7.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/core-js-pure": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.2.tgz", - "integrity": "sha512-DQxdEKm+zFsnON7ZGOgUAQXBt1UJJ01tOzN/HgQ7cNf0oEHW1tcBLfCQQd1q6otdLu5gAdvKYxKHAoXGwE/kiQ==", + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "hasInstallScript": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.2" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -7594,7 +9808,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -7606,17 +9819,12 @@ "node": ">=10" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7626,26 +9834,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, "node_modules/css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -7655,24 +9843,31 @@ } }, "node_modules/css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", + "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", "dev": true, "dependencies": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "boolbase": "^1.0.0", + "css-what": "^5.0.0", + "domhandler": "^4.2.0", + "domutils": "^2.6.0", + "nth-check": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", + "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", "dev": true, "engines": { - "node": "*" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/css.escape": { @@ -7681,25 +9876,10 @@ "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", "dev": true }, - "node_modules/css/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cssfilter": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", "dev": true }, "node_modules/cssom": { @@ -7727,45 +9907,48 @@ "dev": true }, "node_modules/csstype": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", - "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.2.0.tgz", - "integrity": "sha512-aH+kx55J8vRBh4K4k9GN4EbNO3QnZsXy4XBfrnr4fL2gQuszUAPQU3fV2oObO2iSpreRH/bG/wfvO+hDu2+e9w==", - "dependencies": { - "d3-array": "2", - "d3-axis": "2", - "d3-brush": "2", - "d3-chord": "2", - "d3-color": "2", - "d3-contour": "2", - "d3-delaunay": "5", - "d3-dispatch": "2", - "d3-drag": "2", - "d3-dsv": "2", - "d3-ease": "2", - "d3-fetch": "2", - "d3-force": "2", - "d3-format": "2", - "d3-geo": "2", - "d3-hierarchy": "2", - "d3-interpolate": "2", - "d3-path": "2", - "d3-polygon": "2", - "d3-quadtree": "2", - "d3-random": "2", - "d3-scale": "3", - "d3-scale-chromatic": "2", - "d3-selection": "2", - "d3-shape": "2", - "d3-time": "2", - "d3-time-format": "3", - "d3-timer": "2", - "d3-transition": "2", - "d3-zoom": "2" + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.0.tgz", + "integrity": "sha512-a5rNemRadWkEfqnY5NsD4RdCP9vn8EIJ4I5Rl14U0uKH1SXqcNmk/h9aGaAF1O98lz6L9M0IeUcuPa9GUYbI5A==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-array": { @@ -7774,138 +9957,211 @@ "integrity": "sha512-dYWhEvg1L2+osFsSqNHpXaPQNugLT4JfyvbLE046I2PDcgYGFYc0w24GSJwbmcjjZYOPC3PNP2S782bWUM967Q==" }, "node_modules/d3-axis": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.0.0.tgz", - "integrity": "sha512-9nzB0uePtb+u9+dWir+HTuEAKJOEUYJoEwbJPsZ1B4K3iZUgzJcSENQ05Nj7S4CIfbZZ8/jQGoUzGKFznBhiiQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-brush": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-2.1.0.tgz", - "integrity": "sha512-cHLLAFatBATyIKqZOkk/mDHUbzne2B3ZwxkzMHvFTCZCmLaXDpZRihQSn8UNXTkGD/3lb/W2sQz0etAftmHMJQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-chord": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz", - "integrity": "sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-contour": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz", - "integrity": "sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", "dependencies": { - "d3-array": "2" + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", "dependencies": { - "delaunator": "4" + "delaunator": "5" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-dispatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", - "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-drag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz", - "integrity": "sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-selection": "2" + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-dsv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", - "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { - "commander": "2", - "iconv-lite": "0.4", + "commander": "7", + "iconv-lite": "0.6", "rw": "1" }, "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-dsv/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/d3-ease": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz", - "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz", - "integrity": "sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { - "d3-dsv": "1 - 2" + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-force": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", - "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", - "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-geo": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.1.tgz", - "integrity": "sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "dependencies": { - "d3-array": ">=2.5" + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-hierarchy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz", - "integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==" - }, - "node_modules/d3-interpolate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", - "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "dependencies": { - "d3-color": "1 - 2" + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-path": { @@ -7914,52 +10170,81 @@ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, "node_modules/d3-polygon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", - "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-quadtree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", - "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-random": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", - "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "dependencies": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-scale-chromatic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", - "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", "dependencies": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-selection": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", - "integrity": "sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-shape": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.0.0.tgz", - "integrity": "sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "1" } }, "node_modules/d3-time": { @@ -7971,62 +10256,96 @@ } }, "node_modules/d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "dependencies": { - "d3-time": "1 - 2" + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-timer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", - "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-transition": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-2.0.0.tgz", - "integrity": "sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "dependencies": { - "d3-color": "1 - 2", - "d3-dispatch": "1 - 2", - "d3-ease": "1 - 2", - "d3-interpolate": "1 - 2", - "d3-timer": "1 - 2" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, "node_modules/d3-zoom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz", - "integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/d3/node_modules/d3-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", - "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } }, "node_modules/d3/node_modules/d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, + "node_modules/d3/node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dependencies": { - "assert-plus": "^1.0.0" + "d3-path": "^3.1.0" }, "engines": { - "node": ">=0.10" + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/data-urls": { @@ -8044,12 +10363,14 @@ } }, "node_modules/date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "dev": true, - "engines": { - "node": ">=0.11" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/debounce": { @@ -8081,48 +10402,26 @@ "dev": true }, "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true, "engines": { "node": ">=0.10" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, + "node_modules/deeks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/deeks/-/deeks-2.5.1.tgz", + "integrity": "sha512-fqrBeUz7f1UqaXDRzVB5RG2EfPk15HJRrb2pMZj8mLlSTtz4tRPsK5leFOskoHFPuyZ6+7aRM9j657fvXLkJ7Q==", "engines": { - "node": ">=4" + "node": ">= 12" } }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==" }, "node_modules/deep-is": { "version": "0.1.3", @@ -8139,16 +10438,83 @@ } }, "node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", "dev": true, "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" + "execa": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">= 10" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/default-gateway/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/defaults": { @@ -8160,22 +10526,46 @@ "clone": "^1.0.2" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } }, "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "dependencies": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-property": { @@ -8248,9 +10638,12 @@ } }, "node_modules/delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -8265,20 +10658,19 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, "engines": { "node": ">= 0.6" } }, - "node_modules/deprecated-decorator": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", - "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=", - "dev": true - }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-newline": { "version": "3.1.0", @@ -8300,25 +10692,14 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, - "node_modules/dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "dependencies": { - "streamsearch": "0.1.2" - }, - "engines": { - "node": ">=4.5.0" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" + "asap": "^2.0.0", + "wrappy": "1" } }, "node_modules/diff-sequences": { @@ -8349,22 +10730,23 @@ "dev": true }, "node_modules/dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", "dev": true, "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "dependencies": { - "buffer-indexof": "^1.0.0" + "node_modules/doc-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-3.0.2.tgz", + "integrity": "sha512-VRlA2OKSjTbHWj6wmSanxJ338fE/YN8pqmZ0FIWK5JWkIJMFRc4KmD35JtOrnjvVG0WrzOtDDNHx1lN1tkb+lA==", + "engines": { + "node": ">=12" } }, "node_modules/doctrine": { @@ -8380,9 +10762,9 @@ } }, "node_modules/dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz", + "integrity": "sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==", "dev": true }, "node_modules/dom-converter": { @@ -8395,21 +10777,19 @@ } }, "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, "dependencies": { "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", - "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", - "dev": true - }, "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -8417,13 +10797,19 @@ "dev": true }, "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domexception": { - "version": "2.0.1", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", "dev": true, @@ -8444,70 +10830,86 @@ } }, "node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", "dev": true, "dependencies": { - "domelementtype": "1" + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, "node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dot-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", - "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "dependencies": { - "no-case": "^3.0.3", - "tslib": "^1.10.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/dot-case/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true }, "node_modules/dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -8523,9 +10925,10 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.3.642", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz", - "integrity": "sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ==" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "devOptional": true }, "node_modules/emittery": { "version": "0.7.2", @@ -8569,27 +10972,18 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.0.tgz", - "integrity": "sha512-EENz3E701+77g0wfbOITeI8WLPNso2kQNMBIBEi/TH/BEa9YXtS01X7sIEk5XXsfFq1jNkhIpu08hBPH1TRLIQ==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.0.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" } }, - "node_modules/enhanced-resolve/node_modules/tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -8603,15 +10997,18 @@ } }, "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -8620,18 +11017,6 @@ "node": ">=4" } }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8646,27 +11031,34 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "node_modules/es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8675,6 +11067,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -8689,21 +11138,54 @@ "node": ">= 0.4" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/escape-html": { @@ -8715,6 +11197,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -8812,29 +11295,32 @@ } }, "node_modules/eslint": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz", - "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -8842,7 +11328,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -8851,7 +11337,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -8860,46 +11346,70 @@ }, "engines": { "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/eslint-config-standard": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.2.tgz", - "integrity": "sha512-fx3f1rJDsl9bY7qzyX8SAtP8GBSk6MfXFaTfaGgk12aAYW4gJSyRm7dM790L6cbXv63fvjY4XeSzXnb4WM+SKw==", - "dev": true - }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", - "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", "dev": true, "dependencies": { - "debug": "^2.6.9", - "resolve": "^1.13.1" + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/eslint-module-utils": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", - "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", "dev": true, "dependencies": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" + "debug": "^3.2.7", + "find-up": "^2.1.0" }, "engines": { "node": ">=4" } }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/eslint-module-utils/node_modules/find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -8925,6 +11435,12 @@ "node": ">=4" } }, + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/eslint-module-utils/node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -8958,150 +11474,108 @@ "node": ">=4" } }, - "node_modules/eslint-module-utils/node_modules/pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/eslint-plugin-import": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", - "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, "dependencies": { - "array-includes": "^3.1.1", - "array.prototype.flat": "^1.2.3", - "contains-path": "^0.1.0", + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.4", - "eslint-module-utils": "^2.6.0", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.1", - "read-pkg-up": "^2.0.0", - "resolve": "^1.17.0", - "tsconfig-paths": "^3.9.0" + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" }, "engines": { "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "dependencies": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "^2.0.2" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "node_modules/eslint-plugin-jest": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz", - "integrity": "sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg==", + "version": "27.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz", + "integrity": "sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" + "@typescript-eslint/utils": "^5.10.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", - "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", - "dev": true, - "engines": { - "node": ">=6" + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } } }, "node_modules/eslint-plugin-react": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", - "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", "dev": true, "dependencies": { - "array-includes": "^3.1.1", - "array.prototype.flatmap": "^1.2.3", + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", "doctrine": "^2.1.0", - "has": "^1.0.3", + "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "object.entries": "^1.1.2", - "object.fromentries": "^2.0.2", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "resolve": "^1.18.1", - "string.prototype.matchall": "^4.0.2" + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" }, "engines": { "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "dev": true, "engines": { "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { @@ -9116,11 +11590,36 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-standard": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", - "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", - "dev": true + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/eslint-scope": { "version": "5.1.1", @@ -9135,27 +11634,6 @@ "node": ">=8.0.0" } }, - "node_modules/eslint-scope/node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-scope/node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", @@ -9177,6 +11655,15 @@ "node": ">=4" } }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -9221,15 +11708,32 @@ "dev": true }, "node_modules/eslint/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { @@ -9242,15 +11746,18 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/has-flag": { @@ -9278,10 +11785,13 @@ "dev": true }, "node_modules/eslint/node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -9302,22 +11812,36 @@ } }, "node_modules/eslint/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" } }, "node_modules/espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "dependencies": { "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", + "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, "engines": { @@ -9350,9 +11874,9 @@ } }, "node_modules/esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -9362,9 +11886,30 @@ } }, "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -9391,15 +11936,15 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } }, "node_modules/eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "node_modules/events": { @@ -9411,18 +11956,6 @@ "node": ">=0.8.x" } }, - "node_modules/eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "dev": true, - "dependencies": { - "original": "^1.0.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/exec-sh": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", @@ -9448,9 +11981,9 @@ } }, "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "dependencies": { "nice-try": "^1.0.4", @@ -9604,50 +12137,93 @@ "dev": true }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.0.tgz", + "integrity": "sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", + "depd": "2.0.0", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.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.3.0", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/extend-shallow": { "version": "3.0.2", @@ -9674,20 +12250,6 @@ "node": ">=0.10.0" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -9769,15 +12331,6 @@ "node": ">=0.10.0" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, "node_modules/faker": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", @@ -9787,29 +12340,30 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "node_modules/fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -9818,11 +12372,27 @@ "dev": true }, "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -9830,24 +12400,24 @@ "dev": true }, "node_modules/fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, "dependencies": { "websocket-driver": ">=0.5.1" }, "engines": { - "node": ">=0.4.0" + "node": ">=0.8.0" } }, "node_modules/fb-watchman": { @@ -9859,34 +12429,22 @@ "bser": "2.1.1" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=4" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -9895,32 +12453,42 @@ "node": ">=8" } }, - "node_modules/filter-console": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/filter-console/-/filter-console-0.1.1.tgz", - "integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -9998,85 +12566,89 @@ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=4" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flat-cache/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, - "node_modules/fn-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz", - "integrity": "sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==", - "engines": { - "node": ">=8" - } - }, "node_modules/focus-lock": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.8.1.tgz", - "integrity": "sha512-/LFZOIo82WDsyyv7h7oc0MJF9ACOvDRdx9rWPZ2pgMfNWu/z8hQDBtOchuB/0BVLmuFOZjV02YwUVzNsWx/EzA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", "dependencies": { - "tslib": "^1.9.3" + "tslib": "^2.0.3" }, "engines": { "node": ">=10" } }, + "node_modules/focus-lock/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/follow-redirects": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", - "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", - "dev": true, + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/for-in": { @@ -10088,54 +12660,94 @@ "node": ">=0.10.0" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, "engines": { - "node": "*" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true - }, + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "dev": true, + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/formik": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.0.tgz", - "integrity": "sha512-l47RfvejhfHNh8rTRVaCaPfx8nyeYDSTLaEqRvLX4qkWnrrq9ByGVCWggVR+0TVtzc5Ub1gLUuVu9UKuGwfhjA==", + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", + "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], "dependencies": { "deepmerge": "^2.1.1", "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.14", - "lodash-es": "^4.17.14", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "react-fast-compare": "^2.0.1", - "scheduler": "^0.18.0", "tiny-warning": "^1.0.2", "tslib": "^1.10.0" + }, + "peerDependencies": { + "react": ">=16.8.0" } }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { "node": ">= 0.6" } @@ -10192,34 +12804,16 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } }, - "node_modules/fs-capacitor": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz", - "integrity": "sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==", - "dev": true, - "engines": { - "node": ">=8.5" - } - }, - "node_modules/fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - }, - "engines": { - "node": ">=10" - } + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", @@ -10238,6 +12832,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -10247,9 +12842,30 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -10257,16 +12873,20 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "node_modules/fuzzaldrin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", - "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=", - "dev": true + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "devOptional": true, "engines": { "node": ">=6.9.0" } @@ -10281,14 +12901,26 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-nonce": { @@ -10308,6 +12940,18 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -10320,6 +12964,22 @@ "node": ">=6" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -10329,36 +12989,30 @@ "node": ">=0.10.0" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { "is-glob": "^4.0.1" @@ -10383,21 +13037,6 @@ "process": "^0.11.10" } }, - "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dev": true, - "dependencies": { - "ini": "1.3.7" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -10431,100 +13070,73 @@ "node": ">=0.10.0" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "engines": { - "node": ">=8.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/graphql": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz", - "integrity": "sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==", + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", "engines": { - "node": ">= 10.x" + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/graphql-extensions": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.15.0.tgz", - "integrity": "sha512-bVddVO8YFJPwuACn+3pgmrEg6I8iBuYLuwvxiE+lcQQ7POotVZxm2rgGw0PvVYmWWf3DT7nTVDZ5ROh/ALp8mA==", - "deprecated": "The `graphql-extensions` API has been removed from Apollo Server 3. Use the plugin API instead: https://www.apollographql.com/docs/apollo-server/integrations/plugins/", + "node_modules/graphql-relay": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.0.tgz", + "integrity": "sha512-44yBuw2/DLNEiMypbNZBt1yMDbBmyVPVesPywnteGGALiBmdyy1JP8jSg8ClLePg8ZZxk0O4BLhd1a6U/1jDOQ==", "dev": true, - "dependencies": { - "@apollographql/apollo-tools": "^0.5.0", - "apollo-server-env": "^3.1.0", - "apollo-server-types": "^0.9.0" - }, "engines": { - "node": ">=6.0" + "node": "^12.20.0 || ^14.15.0 || >= 15.9.0" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^16.2.0" } }, - "node_modules/graphql-relay": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.6.0.tgz", - "integrity": "sha512-OVDi6C9/qOT542Q3KxZdXja3NrDvqzbihn1B44PH8P/c5s0Q90RyQwT6guhGqXqbYEH6zbeLJWjQqiYvcg2vVw==", + "node_modules/graphql-subscriptions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz", + "integrity": "sha512-s6k2b8mmt9gF9pEfkxsaO1lTxaySfKoEJzEfmwguBbQ//Oq23hIXCfR1hm4kdh5hnR20RdwB+s3BCb+0duHSZA==", "dev": true, "dependencies": { - "prettier": "^1.16.0" + "iterall": "^1.3.0" }, "peerDependencies": { - "graphql": "^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0-b || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^15.7.2 || ^16.0.0" } }, - "node_modules/graphql-relay/node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "dependencies": { + "tslib": "^2.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/graphql-subscriptions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz", - "integrity": "sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g==", - "dev": true, - "dependencies": { - "iterall": "^1.3.0" + "node": ">=10" }, "peerDependencies": { - "graphql": "^0.10.5 || ^0.11.3 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/graphql-tag": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz", - "integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==" + "node_modules/graphql-tag/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/growly": { "version": "1.3.0", @@ -10539,28 +13151,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -10573,9 +13163,9 @@ } }, "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10585,15 +13175,42 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, "engines": { "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -10664,13 +13281,15 @@ "node": ">=0.10.0" } }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, "node_modules/he": { @@ -10687,19 +13306,6 @@ "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -10769,9 +13375,9 @@ } }, "node_modules/html-entities": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", - "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "node_modules/html-escaper": { @@ -10781,78 +13387,77 @@ "dev": true }, "node_modules/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, "dependencies": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", - "param-case": "^3.0.3", + "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^4.6.3" + "terser": "^5.10.0" }, "bin": { "html-minifier-terser": "cli.js" }, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/html-minifier-terser/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 12" } }, "node_modules/html-webpack-plugin": { - "version": "5.0.0-beta.6", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.0.0-beta.6.tgz", - "integrity": "sha512-wjdhOnJlo5c8uN3OahRm2eaLKL8gEQ4ZNOkwQc8BStyudpFLTsg28m6wbf00keXiRPesk2Pad9CYeKpxbffApg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, "dependencies": { - "@types/html-minifier-terser": "^5.0.0", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^2.0.0", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", "tapable": "^2.0.0" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" } }, "node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -10860,20 +13465,47 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -10888,157 +13520,104 @@ "node": ">=8.0.0" } }, - "node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=4.0.0" + "node": ">= 6" } }, - "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" + "node": ">=6.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/http-proxy-middleware/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, + "license": "MIT", "dependencies": { - "is-extendable": "^0.1.0" + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" + "node": ">=12.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" + "peerDependencies": { + "@types/express": "^4.17.13" }, - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, - "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "node_modules/http-proxy-middleware/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "ms": "2.1.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "node": ">=6.0" }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -11066,9 +13645,9 @@ "dev": true }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -11081,24 +13660,18 @@ "dev": true }, "node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" }, "engines": { "node": ">=6" - } - }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/import-local": { @@ -11206,129 +13779,31 @@ "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true - }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "dependencies": { - "es-abstract": "^1.17.0-next.1", + "get-intrinsic": "^1.1.0", "has": "^1.0.3", - "side-channel": "^1.0.2" + "side-channel": "^1.0.4" }, "engines": { "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -11338,6 +13813,20 @@ "node": ">= 0.10" } }, + "node_modules/intro.js": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.2.0.tgz", + "integrity": "sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ==" + }, + "node_modules/intro.js-react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/intro.js-react/-/intro.js-react-1.0.0.tgz", + "integrity": "sha512-zR8pbTyX20RnCZpJMc0nuHBpsjcr1wFkj3ZookV6Ly4eE/LGpFTQwPsaA61Cryzwiy/tTFsusf4hPU9NpI9UOg==", + "peerDependencies": { + "intro.js": ">=2.5.0", + "react": ">=0.14.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -11346,21 +13835,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -11369,15 +13843,6 @@ "node": ">= 0.10" } }, - "node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -11402,59 +13867,38 @@ "node": ">=0.10.0" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "node_modules/is-alphanumerical": { + "node_modules/is-bigint": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "node_modules/is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true, + "has-bigints": "^1.0.1" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "dependencies": { - "binary-extensions": "^1.0.0" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -11470,9 +13914,9 @@ "dev": true }, "node_modules/is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "engines": { "node": ">= 0.4" @@ -11494,11 +13938,14 @@ } }, "node_modules/is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dependencies": { "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-data-descriptor": { @@ -11534,12 +13981,6 @@ "node": ">= 0.4" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, "node_modules/is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -11568,7 +14009,6 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", "dev": true, - "optional": true, "bin": { "is-docker": "cli.js" }, @@ -11613,9 +14053,9 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" @@ -11624,31 +14064,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-installed-globally/node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -11658,10 +14073,15 @@ "node": ">=8" } }, + "node_modules/is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==" + }, "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true, "engines": { "node": ">= 0.4" @@ -11670,15 +14090,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11689,10 +14100,13 @@ } }, "node_modules/is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -11700,13 +14114,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/is-observable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz", + "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-path-cwd": { @@ -11742,6 +14160,18 @@ "node": ">=6" } }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -11755,19 +14185,19 @@ } }, "node_modules/is-potential-custom-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", - "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, "node_modules/is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -11776,6 +14206,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -11786,12 +14228,18 @@ } }, "node_modules/is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-symbol": { @@ -11806,12 +14254,39 @@ "node": ">= 0.4" } }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -11826,7 +14301,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "optional": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -11834,16 +14308,11 @@ "node": ">=8" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -11869,12 +14338,6 @@ "unfetch": "^4.2.0" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, "node_modules/istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", @@ -11900,9 +14363,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -12000,7 +14463,23 @@ "node_modules/iterall": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/jest": { "version": "26.6.3", @@ -12457,57 +14936,11 @@ } }, "node_modules/jest-emotion": { - "version": "10.0.32", - "resolved": "https://registry.npmjs.org/jest-emotion/-/jest-emotion-10.0.32.tgz", - "integrity": "sha512-hW3IwWc47qRuxnGsWFGY6uIMX8F4YBzq+Qci3LAYUCUqUBNP+1DU1L5Nudo9Ry0NHVFOqDnDeip1p2UR0kVMwA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.5.5", - "@types/jest": "^23.0.2", - "chalk": "^2.4.1", - "css": "^2.2.1" - } - }, - "node_modules/jest-emotion/node_modules/@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/jest-emotion/-/jest-emotion-11.0.0.tgz", + "integrity": "sha512-nw+iYPcHM+H5huUIsDh53uEIp6SrN5v+MOxa7qyXReae9foCTACDkFuF/1Hfkik/R8mDMir7q5QEiVMRJ0/f2Q==", "dev": true }, - "node_modules/jest-emotion/node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "node_modules/jest-emotion/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-emotion/node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "node_modules/jest-environment-jsdom": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", @@ -13420,9 +15853,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13691,14 +16124,14 @@ } }, "node_modules/jest-worker": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", - "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "supports-color": "^8.0.0" }, "engines": { "node": ">= 10.13.0" @@ -13714,26 +16147,44 @@ } }, "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-sha256": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz", + "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "dependencies": { "argparse": "^1.0.7", @@ -13743,20 +16194,14 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, "node_modules/jsdom": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.2.tgz", - "integrity": "sha512-JxNtPt9C1ut85boCbJmffaQ06NBnzkQY/MWO3YxPW8IWS38A26z+B1oBvA9LwKrytewdfymnhi4UNH3/RAgZrg==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", "dev": true, "dependencies": { "abab": "^2.0.5", - "acorn": "^8.1.0", + "acorn": "^8.2.4", "acorn-globals": "^6.0.0", "cssom": "^0.4.4", "cssstyle": "^2.3.0", @@ -13764,12 +16209,13 @@ "decimal.js": "^10.2.1", "domexception": "^2.0.1", "escodegen": "^2.0.0", + "form-data": "^3.0.0", "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.0", "parse5": "6.0.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.9", "saxes": "^5.0.1", "symbol-tree": "^3.2.4", "tough-cookie": "^4.0.0", @@ -13779,11 +16225,35 @@ "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", "whatwg-url": "^8.5.0", - "ws": "^7.4.4", + "ws": "^7.4.6", "xml-name-validator": "^3.0.0" }, "engines": { "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" } }, "node_modules/jsesc": { @@ -13797,22 +16267,22 @@ "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "node_modules/json-2-csv": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-3.17.1.tgz", + "integrity": "sha512-i6QynVy42GGMgY8fYde0mp6nYteptvk8oJsphOLiT3CITzw7NBBAiRwHV35kDOBii/elDQe1HCWLqaBPJ3istQ==", + "dependencies": { + "deeks": "2.5.1", + "doc-path": "3.0.2" + }, + "engines": { + "node": ">= 12" + } }, - "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -13826,25 +16296,11 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, "node_modules/json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dependencies": { - "minimist": "^1.2.5" - }, + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "devOptional": true, "bin": { "json5": "lib/cli.js" }, @@ -13852,36 +16308,20 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" - } - }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dev": true, "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/ms": { @@ -13890,19 +16330,19 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "engines": [ - "node >=0.6.0" - ], "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/jsx-ast-utils": { @@ -13919,41 +16359,26 @@ } }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "dev": true, "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "dev": true, "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -13972,23 +16397,21 @@ "node": ">=6" } }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "node_modules/launch-editor": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "dev": true, "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -14011,55 +16434,23 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, - "node_modules/load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz", - "integrity": "sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -14070,97 +16461,37 @@ "node": ">=8.9.0" } }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "node_modules/lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", - "dev": true - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", - "dev": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", - "dev": true - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", - "dev": true - }, "node_modules/lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", @@ -14169,7 +16500,7 @@ "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, "node_modules/lodash.startswith": { @@ -14177,6 +16508,12 @@ "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "node_modules/log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -14254,12 +16591,16 @@ } }, "node_modules/loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", "dev": true, "engines": { "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" } }, "node_modules/long": { @@ -14294,15 +16635,6 @@ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", "dev": true }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -14337,24 +16669,18 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "node_modules/make-plural": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.2.tgz", - "integrity": "sha512-8iTuFioatnTTmb/YJjywkVIHLjcwkFD9Ms0JpxjEm9Mo8eQYkh1z+55dwv4yc1jQ8ftVBxWQbihvZL1DfzGGWA==" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.1.0.tgz", + "integrity": "sha512-PKkwVlAxYVo98NrbclaQIT4F5Oy+X58PZM5r2IwUSCe3syya6PXkIRCn2XCdz7p58Scgpp50PBeHmepXVDG3hg==" }, "node_modules/makeerror": { "version": "1.0.11", @@ -14387,9 +16713,17 @@ } }, "node_modules/math-expression-evaluator": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.8.tgz", - "integrity": "sha512-9FbRY3i6U+CbHgrdNbAUaisjWTozkm1ZfupYQJiZ87NtYHk2Zh9DvxMgp/fifxVhqTLpd5fCCLossUbpZxGeKw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.4.0.tgz", + "integrity": "sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } }, "node_modules/media-typer": { "version": "0.3.0", @@ -14399,55 +16733,25 @@ "node": ">= 0.6" } }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/memory-fs/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/memory-fs/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/memory-fs/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/memfs": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "fs-monkey": "1.0.3" + }, + "engines": { + "node": ">= 4.0.0" } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -14464,11 +16768,6 @@ "node": ">= 8" } }, - "node_modules/messageformat-parser": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", - "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==" - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -14478,16 +16777,17 @@ } }, "node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=8.6" } }, "node_modules/mime": { @@ -14502,19 +16802,19 @@ } }, "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -14529,19 +16829,10 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.1.tgz", + "integrity": "sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g==", "dev": true, "dependencies": { "dom-walk": "^0.1.0" @@ -14556,15 +16847,6 @@ "node": ">=4" } }, - "node_modules/mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -14572,9 +16854,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -14584,9 +16866,20 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -14613,17 +16906,10 @@ "node": ">=0.10.0" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" }, "node_modules/mq-polyfill": { "version": "1.1.8", @@ -14640,29 +16926,22 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, "dependencies": { - "dns-packet": "^1.3.1", + "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "node_modules/nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" }, "node_modules/nanomatch": { "version": "1.2.13", @@ -14693,9 +16972,9 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } @@ -14728,30 +17007,57 @@ "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", "dev": true }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true, - "engines": { - "node": ">= 6.0.0" + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/node-gettext": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", - "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, - "dependencies": { - "lodash.get": "^4.4.2" + "engines": { + "node": ">= 6.13.0" } }, "node_modules/node-int64": { @@ -14785,9 +17091,9 @@ } }, "node_modules/node-notifier/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "optional": true, "dependencies": { @@ -14801,27 +17107,27 @@ } }, "node_modules/node-releases": { - "version": "1.1.70", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", - "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==" + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "devOptional": true }, "node_modules/nodemon": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", - "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", "dev": true, - "hasInstallScript": true, "dependencies": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", + "chokidar": "^3.5.2", + "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" @@ -14834,6 +17140,27 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/nodemon/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -14885,15 +17212,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -14916,12 +17234,15 @@ } }, "node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "dev": true, "dependencies": { - "boolbase": "~1.0.0" + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/nwsapi": { @@ -14930,15 +17251,6 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -14986,25 +17298,15 @@ } }, "node_modules/object-inspect": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", - "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - }, + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-keys": { @@ -15016,15 +17318,6 @@ "node": ">= 0.4" } }, - "node_modules/object-path": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.5.tgz", - "integrity": "sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==", - "dev": true, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -15056,46 +17349,44 @@ } }, "node_modules/object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", "dev": true, "dependencies": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" + "es-abstract": "^1.19.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", "dev": true, "dependencies": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "es-abstract": "^1.19.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "node_modules/object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.8" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15114,20 +17405,29 @@ } }, "node_modules/object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "dependencies": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "es-abstract": "^1.19.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/observable-fns": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.6.1.tgz", + "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==", + "dev": true, + "license": "MIT" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -15135,9 +17435,9 @@ "dev": true }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -15146,10 +17446,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "engines": { "node": ">= 0.8" } @@ -15164,42 +17463,41 @@ } }, "node_modules/onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", "dev": true, "dependencies": { - "is-wsl": "^1.1.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/opn/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optimism": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.15.0.tgz", - "integrity": "sha512-KLKl3Kb7hH++s9ewRcBhmfpXgXF0xQ+JZ3xQFuPjnoT6ib2TDmYyVkKENmGxivsN2G3VRxpXuauCkB4GYOhtPw==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.1.tgz", + "integrity": "sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==", "dependencies": { "@wry/context": "^0.6.0", "@wry/trie": "^0.3.0" @@ -15305,33 +17603,6 @@ "node": ">=8" } }, - "node_modules/original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "dependencies": { - "url-parse": "^1.4.3" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -15362,18 +17633,6 @@ "node": ">=6" } }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", @@ -15384,15 +17643,16 @@ } }, "node_modules/p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "dev": true, "dependencies": { - "retry": "^0.12.0" + "@types/retry": "0.12.0", + "retry": "^0.13.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/p-try": { @@ -15404,46 +17664,29 @@ "node": ">=6" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/papaparse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.0.tgz", - "integrity": "sha512-Lb7jN/4bTpiuGPrYy4tkKoUS8sTki8zacB5ke1p5zolhcSE4TlWgrlsxjrDTbG/dFVh07ck7X36hUf/b5V68pg==", - "dev": true + "license": "BlueOak-1.0.0" }, "node_modules/param-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", - "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, "dependencies": { - "dot-case": "^3.0.3", - "tslib": "^1.10.0" + "dot-case": "^3.0.4", + "tslib": "^2.0.3" } }, + "node_modules/param-case/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15456,17 +17699,20 @@ } }, "node_modules/parse-json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", - "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parse5": { @@ -15508,12 +17754,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -15548,14 +17788,41 @@ } }, "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" }, "node_modules/path-type": { "version": "4.0.0", @@ -15565,19 +17832,21 @@ "node": ">=8" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "engines": { "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pify": { @@ -15622,41 +17891,12 @@ "node": ">= 6" } }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/plurals-cldr": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/plurals-cldr/-/plurals-cldr-1.0.4.tgz", - "integrity": "sha512-4nLXqtel7fsCgzi8dvRZvUjfL8SXpP982sKg7b2TgpnR8rDnes06iuQ83trQ/+XdtyMIQkBBbKzX6x97eLfsJQ==", - "dev": true - }, "node_modules/pofile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.0.tgz", - "integrity": "sha512-6XYcNkXWGiJ2CVXogTP7uJ6ZXQCldYLZc16wgRp8tqRaBTTyIfF+TUT3EQJPXTLAT7OTPpTAoaFdoXKfaTRU1w==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz", + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==", + "dev": true, + "license": "MIT" }, "node_modules/popmotion": { "version": "9.3.6", @@ -15674,47 +17914,16 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, - "node_modules/portfinder": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.27.tgz", - "integrity": "sha512-bJ3U3MThKnyJ9Dx1Idtm5pQmxXqw08+XOHhi/Lie8OF1OlhVaBFhsntAIhkZYjfDcCzszSr0w1yCbccThhzgxQ==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" } }, - "node_modules/portfinder/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -15724,6 +17933,15 @@ "node": ">=0.10.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15733,35 +17951,29 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, "dependencies": { "lodash": "^4.17.20", - "renderkid": "^2.0.4" + "renderkid": "^3.0.0" } }, "node_modules/pretty-format": { @@ -15853,13 +18065,13 @@ } }, "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "node_modules/property-expr": { @@ -15868,30 +18080,30 @@ "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" }, "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { "node": ">= 0.10" } }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, "node_modules/pseudolocale": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.2.0.tgz", - "integrity": "sha512-k0OQFvIlvpRdzR0dPVrrbWX7eE9EaZ6gpZtTlFSDi1Gf9tMy9wiANCNu7JZ0drcKgUri/39a2mBbH0goiQmrmQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-2.1.0.tgz", + "integrity": "sha512-af5fsrRvVwD+MBasBJvuDChT0KDqT0nEwD9NTgbtHJ16FKomWac9ua0z6YVNB4G9x9IOaiGWym62aby6n4tFMA==", "dev": true, "dependencies": { - "commander": "*" + "commander": "^10.0.0" + }, + "bin": { + "pseudolocale": "dist/cli.mjs" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/psl": { @@ -15925,55 +18137,45 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dependencies": { - "escape-goat": "^2.0.0" + "side-channel": "^1.1.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "engines": { "node": ">=0.6" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true, - "engines": { - "node": ">=0.4.x" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - }, - "node_modules/ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/range-parser": { "version": "1.2.1", @@ -15984,91 +18186,102 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, + "node_modules/raw-body/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, - "bin": { - "rc": "cli.js" + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, + "node_modules/raw-body/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, "node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-clientside-effect": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz", - "integrity": "sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", "dependencies": { "@babel/runtime": "^7.12.13" }, "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.0.0" + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "node_modules/react-dom/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" } }, "node_modules/react-error-boundary": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.3.tgz", - "integrity": "sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -16085,20 +18298,69 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "node_modules/react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "dependencies": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-floater/node_modules/@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==" + }, + "node_modules/react-floater/node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-floater/node_modules/is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" + }, + "node_modules/react-floater/node_modules/tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + }, "node_modules/react-focus-lock": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.0.tgz", - "integrity": "sha512-XLxj6uTXgz0US8TmqNU2jMfnXwZG0mH2r/afQqvPEaX6nyEll5LHVcEXk2XDUQ34RVeLPkO/xK5x6c/qiuSq/A==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.12.1.tgz", + "integrity": "sha512-lfp8Dve4yJagkHiFrC1bGtib3mF2ktqwPJw4/WGcgPW+pJ/AVQA5X2vI7xgp13FcxFEpYBBHpXai/N2DBNC0Jw==", "dependencies": { "@babel/runtime": "^7.0.0", - "focus-lock": "^0.8.1", + "focus-lock": "^1.3.5", "prop-types": "^15.6.2", - "react-clientside-effect": "^1.2.2", - "use-callback-ref": "^1.2.1", - "use-sidecar": "^1.0.1" + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/react-hot-loader": { @@ -16121,9 +18383,9 @@ } }, "node_modules/react-hot-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -16133,9 +18395,9 @@ } }, "node_modules/react-hot-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -16155,15 +18417,13 @@ "node": ">= 8" } }, - "node_modules/react-input-autosize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", - "integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==", - "dependencies": { - "prop-types": "^15.5.8" - }, + "node_modules/react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", "peerDependencies": { - "react": "^16.3.0 || ^17.0.0" + "@types/react": ">=0.0.0 <=99", + "react": ">=0.0.0 <=99" } }, "node_modules/react-is": { @@ -16171,6 +18431,47 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-joyride": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.8.2.tgz", + "integrity": "sha512-2QY8HB1G0I2OT0PKMUz7gg2HAjdkG2Bqi13r0Bb1V16PAwfb9khn4wWBTOJsGsjulbAWiQ3/0YrgNUHGFmuifw==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.18.2" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-joyride/node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-joyride/node_modules/type-fest": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", + "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -16178,9 +18479,9 @@ "dev": true }, "node_modules/react-phone-input-2": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", - "integrity": "sha512-gOY3jUpwO7ulryXPEdqzH7L6DPqI9RQxKfBxZbgqAwXyALGsmwLWFyi2RQwXlBLWN/EPPT4Nv6I9TESVY2YBcg==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz", + "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==", "dependencies": { "classnames": "^2.2.6", "lodash.debounce": "^4.0.8", @@ -16190,27 +18491,27 @@ "prop-types": "^15.7.2" }, "peerDependencies": { - "react": "^16.12.0 || ^17.0.0", - "react-dom": "^16.12.0 || ^17.0.0" + "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", + "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" } }, "node_modules/react-remove-scroll": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz", - "integrity": "sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz", + "integrity": "sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==", "dependencies": { - "react-remove-scroll-bar": "^2.1.0", - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0", - "use-callback-ref": "^1.2.3", - "use-sidecar": "^1.0.1" + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" }, "engines": { - "node": ">=8.5.0" + "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -16219,19 +18520,19 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz", - "integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", "dependencies": { - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0" + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" }, "engines": { - "node": ">=8.5.0" + "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -16239,102 +18540,74 @@ } } }, - "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } + "node_modules/react-remove-scroll-bar/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/react-router/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } + "node_modules/react-remove-scroll/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/react-select": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz", - "integrity": "sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==", + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", "dependencies": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.1.1", - "memoize-one": "^5.0.0", - "prop-types": "^15.6.0", - "react-input-autosize": "^3.0.0", - "react-transition-group": "^4.3.0" + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" + "react": ">=16.8" } }, - "node_modules/react-select/node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" } }, - "node_modules/react-select/node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" }, "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-style-singleton": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz", - "integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", "dependencies": { "get-nonce": "^1.0.0", "invariant": "^2.2.4", - "tslib": "^1.0.0" + "tslib": "^2.0.0" }, "engines": { - "node": ">=8.5.0" + "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -16342,932 +18615,872 @@ } } }, - "node_modules/react-swipe": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/react-swipe/-/react-swipe-6.0.4.tgz", - "integrity": "sha512-NIF+gVOqPpE8GyCg0ssFC+fPgeqCwNnvqFU/A8nDAOvoncW3KjSVFwgkYNnErHvpFZGmsVw4SLWK96n7+mnChg==", - "dependencies": { - "lodash.isequal": "^4.5.0", - "prop-types": "^15.6.0", - "swipe-js-iso": "^2.1.5" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.5.1" - } + "node_modules/react-style-singleton/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/react-table": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.0.tgz", - "integrity": "sha512-16kRTypBWz9ZwLnPWA8hc3eIC64POzO9GaMBiKaCcVM+0QOQzt0G7ebzGUM8SW0CYUpVM+glv1kMXrWj9tr3Sw==" + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", + "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" + } }, "node_modules/react-test-renderer": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", - "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", + "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", "dev": true, "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" } }, - "node_modules/react-test-renderer/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, "node_modules/react-use-measure": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.4.tgz", - "integrity": "sha512-7K2HIGaPMl3Q9ZQiEVjen3tRXl4UDda8LiTPy/QxP8dP2rl5gPBhf7mMH6MVjjRNv3loU7sNzey/ycPNnHVTxQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", "dependencies": { - "debounce": "^1.2.0" + "debounce": "^1.2.1" }, "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" } }, - "node_modules/read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "dependencies": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=4" + "node": ">= 6" } }, - "node_modules/read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=4" + "node": ">=8.10.0" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "node_modules/rechoir": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", + "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", "dev": true, "dependencies": { - "locate-path": "^2.0.0" + "resolve": "^1.9.0" }, "engines": { - "node": ">=4" + "node": ">= 0.10" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { + "node_modules/reduce-css-calc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==", + "dependencies": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + } + }, + "node_modules/reduce-css-calc/node_modules/balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==" + }, + "node_modules/reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "dependencies": { - "p-try": "^1.0.0" + "regenerate": "^1.4.2" }, "engines": { "node": ">=4" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "dev": true, "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" + "@babel/runtime": "^7.8.4" } }, - "node_modules/read-pkg-up/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "dependencies": { - "pify": "^2.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-pkg/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/regexpu-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" }, "engines": { - "node": ">= 6" + "node": ">=4" } }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "node_modules/regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "jsesc": "~0.5.0" }, - "engines": { - "node": ">=0.10" + "bin": { + "regjsparser": "bin/parser" } }, - "node_modules/readdirp/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "bin": { + "jsesc": "bin/jsesc" } }, - "node_modules/readdirp/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" } }, - "node_modules/readdirp/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" } }, - "node_modules/readdirp/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/readdirp/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10" } }, - "node_modules/readdirp/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/readdirp/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/readdirp/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/readdirp/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, - "node_modules/readdirp/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true }, - "node_modules/readdirp/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "dependencies": { - "resolve": "^1.9.0" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/reduce-function-call": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", - "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", - "dependencies": { - "balanced-match": "^1.0.0" + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" } }, - "node_modules/regenerate": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", - "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, - "node_modules/regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "dependencies": { - "regenerate": "^1.4.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" + "engines": { + "node": ">= 4" } }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, "engines": { + "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "glob": "^7.1.3" }, - "engines": { - "node": ">= 0.4" + "bin": { + "rimraf": "bin.js" } }, - "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "node_modules/robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, + "node_modules/rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true, "engines": { - "node": ">=8" + "node": "6.* || >= 7.*" } }, - "node_modules/regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - }, - "engines": { - "node": ">=4" + "queue-microtask": "^1.2.2" } }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" + "ret": "~0.1.10" } }, - "node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "node_modules/sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", "dev": true, "dependencies": { - "jsesc": "~0.5.0" + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" }, "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true, + "sane": "src/cli.js" + }, "engines": { - "node": ">= 0.10" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/renderkid": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", - "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", + "node_modules/sane/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "dependencies": { - "css-select": "^1.1.0", - "dom-converter": "^0.2", - "htmlparser2": "^3.3.0", - "lodash": "^4.17.20", - "strip-ansi": "^3.0.0" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, - "node_modules/renderkid/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "node_modules/sane/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/renderkid/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" + "is-extendable": "^0.1.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "node_modules/sane/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, "engines": { - "node": ">=0.10" + "node": ">=0.10.0" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "node_modules/sane/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "node_modules/sane/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "dependencies": { - "lodash": "^4.17.19" + "is-buffer": "^1.1.5" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "node_modules/sane/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" }, "engines": { - "node": ">=0.12.0" + "node": ">=0.10.0" } }, - "node_modules/request-promise-native/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "node_modules/sane/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "remove-trailing-separator": "^1.0.1" }, "engines": { - "node": ">=0.8" + "node": ">=0.10.0" } }, - "node_modules/request/node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "node_modules/sane/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, "engines": { - "node": ">=0.6" + "node": ">=0.10.0" } }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", "dev": true, "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "xmlchars": "^2.2.0" }, "engines": { - "node": ">=0.8" + "node": ">=10" } }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 8.9.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "node_modules/scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + "node_modules/scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" }, - "node_modules/resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", - "dependencies": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" - } + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, "dependencies": { - "resolve-from": "^5.0.0" + "@types/node-forge": "^1.3.0", + "node-forge": "^1" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "engines": { - "node": ">=8" + "bin": { + "semver": "bin/semver" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">=0.12" + "node": ">= 0.8" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, - "engines": { - "node": ">= 4" - } + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "dependencies": { - "glob": "^7.1.3" + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true, "engines": { - "node": "6.* || >= 7.*" + "node": ">= 0.8.0" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.6" } }, - "node_modules/run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, - "node_modules/rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "dev": true, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "tslib": "^1.9.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "npm": ">=2.0.0" + "node": ">= 0.8.0" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "node": ">= 0.4" } }, - "node_modules/sane/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { + "node_modules/set-value/node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", @@ -17279,455 +19492,307 @@ "node": ">=0.10.0" } }, - "node_modules/sane/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "dev": true, "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/sha.js/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/sha.js/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/sane/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "kind-of": "^6.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/sane/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sane/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true, + "optional": true + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/scheduler": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", - "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { - "node": ">= 8.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "node_modules/selfsigned": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", - "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", "dev": true, "dependencies": { - "node-forge": "^0.10.0" + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" } }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", "dev": true, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, "engines": { "node": ">=8" } }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.6" + "node": ">=7.0.0" } }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=0.10.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "dev": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "node_modules/side-channel": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", - "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", - "dev": true, - "dependencies": { - "es-abstract": "^1.18.0-next.0", - "object-inspect": "^1.8.0" - } - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "engines": { "node": ">=0.10.0" @@ -17845,72 +19910,16 @@ } }, "node_modules/sockjs": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", - "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.10.0", - "uuid": "^3.4.0", - "websocket-driver": "0.6.5" - } - }, - "node_modules/sockjs-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", - "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", - "dev": true, - "dependencies": { - "debug": "^3.2.5", - "eventsource": "^1.0.7", - "faye-websocket": "~0.11.1", - "inherits": "^2.0.3", - "json3": "^3.3.2", - "url-parse": "^1.4.3" - } - }, - "node_modules/sockjs-client/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/sockjs-client/node_modules/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/sockjs-client/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" } }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -17920,39 +19929,39 @@ } }, "node_modules/source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-loader": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-2.0.1.tgz", - "integrity": "sha512-UzOTTQhoNPeTNzOxwFw220RSRzdGSyH4lpNyWjR7Qm34P4/N0W669YSUFdH07+YNeN75h765XLHmNsF/bm97RQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", "dev": true, "dependencies": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.2", - "source-map-js": "^0.6.2" + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.72.1" } }, "node_modules/source-map-loader/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -17961,20 +19970,10 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -18059,12 +20058,20 @@ } }, "node_modules/spdy-transport/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/spdy-transport/node_modules/ms": { @@ -18074,12 +20081,20 @@ "dev": true }, "node_modules/spdy/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/spdy/node_modules/ms": { @@ -18106,31 +20121,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -18181,36 +20171,9 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", - "dev": true, - "engines": { - "node": ">=0.8.0" + "node": ">= 0.6" } }, "node_modules/string_decoder": { @@ -18242,53 +20205,101 @@ } }, "node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", - "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "has-symbols": "^1.0.1", - "internal-slot": "^1.0.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -18340,6 +20351,9 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/style-value-types": { @@ -18357,98 +20371,53 @@ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, "node_modules/stylis": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz", - "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==" - }, - "node_modules/subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", - "dependencies": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependencies": { - "graphql": ">=0.10.0" - } - }, - "node_modules/subscriptions-transport-ws/node_modules/eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "node_modules/subscriptions-transport-ws/node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/subscriptions-transport-ws/node_modules/ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dependencies": { - "async-limiter": "~1.0.0" - } + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.0.tgz", + "integrity": "sha512-iudipXEel+SzlP9y29UBWGDjB+Zzag+eeA1iLosaR2YHBRr1Q1kC29iBrF2zIVD9fqVbpZnXkN/VJmwFMVyNWg==", "dev": true, "dependencies": { "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.0.1", "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", + "mime": "2.6.0", + "qs": "^6.10.3", "readable-stream": "^3.6.0", - "semver": "^7.3.2" + "semver": "^7.3.7" }, "engines": { - "node": ">= 7.0.0" + "node": ">=6.4.0 <13 || >=14" } }, "node_modules/superagent/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" - } - }, - "node_modules/superagent/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" }, - "engines": { - "node": ">= 6" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/superagent/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "bin": { "mime": "cli.js" @@ -18463,22 +20432,10 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/superagent/node_modules/qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - } - }, "node_modules/superagent/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -18490,34 +20447,24 @@ "node": ">=10" } }, - "node_modules/superagent/node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, "node_modules/supertest": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.3.tgz", - "integrity": "sha512-v2NVRyP73XDewKb65adz+yug1XMtmvij63qIWHZzSX8tp6wiq6xBLUy4SUAd2NII6wIipOmHT/FD9eicpJwdgQ==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.4.tgz", + "integrity": "sha512-M8xVnCNv+q2T2WXVzxDECvL2695Uv2uUj2O0utxsld/HRyJvOU8W9f1gvsYxSNU4wmIe0/L/ItnpU4iKq0emDA==", "dev": true, "dependencies": { "methods": "^1.1.2", - "superagent": "^6.1.0" + "superagent": "^8.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.4.0" } }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -18559,19 +20506,21 @@ "node": ">=8" } }, - "node_modules/swipe-js-iso": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/swipe-js-iso/-/swipe-js-iso-2.1.5.tgz", - "integrity": "sha512-yTTU5tDYEvtKfCD8PN+Rva25acJwogUCd6wPT1n1im/MOJlg6PtHiPKaaNK7HDBiIrWThA/WRbJZbox2letghg==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "engines": { - "node": ">=8.0.0", - "npm": ">=5.5.1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/symbol-observable": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", - "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "engines": { "node": ">=0.10" } @@ -18582,95 +20531,55 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/synchronous-promise": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.13.tgz", - "integrity": "sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==" - }, "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz", + "integrity": "sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw==", "dev": true, "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=10.0.0" } }, - "node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "node_modules/table/node_modules/ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dev": true, "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, "node_modules/tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "engines": { "node": ">=6" - } - }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true, - "engines": { - "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terminal-link": { @@ -18687,104 +20596,107 @@ } }, "node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, "node_modules/terser-webpack-plugin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.0.tgz", - "integrity": "sha512-rf7l5a9xamIVX3enQeTl0MY2MNeZClo5yPX/tVPy22oY0nzu0b45h7JqyFi/bygqKWtzXMnml0u12mArhQPsBQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "dependencies": { - "jest-worker": "^26.5.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.3.5" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">= 10.13.0" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", - "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "bin": { - "terser": "bin/terser" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser/node_modules/commander": { @@ -18793,15 +20705,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -18822,18 +20725,56 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/threads": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/threads/-/threads-1.7.0.tgz", + "integrity": "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0", + "debug": "^4.2.0", + "is-observable": "^2.1.0", + "observable-fns": "^0.6.1" + }, + "funding": { + "url": "https://github.com/andywer/threads.js?sponsor=1" + }, + "optionalDependencies": { + "tiny-worker": ">= 2" + } + }, + "node_modules/threads/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/threads/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -18850,40 +20791,102 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", - "engines": { - "node": "*" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "node_modules/tiny-worker": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tiny-worker/-/tiny-worker-2.3.0.tgz", + "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "esm": "^3.2.25" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "dependencies": { - "os-tmpdir": "~1.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "engines": { - "node": ">=0.6.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dev": true, + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, + "node_modules/to-buffer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -18908,15 +20911,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", @@ -18947,12 +20941,12 @@ "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } @@ -18975,23 +20969,24 @@ } }, "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { "node": ">=6" } }, "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "engines": { "node": ">= 4.0.0" @@ -19009,10 +21004,19 @@ "node": ">=8" } }, + "node_modules/tree-changes": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.2.tgz", + "integrity": "sha512-4gXlUthrl+RabZw6lLvcCDl6KfJOCmrC16BC5CRdut1EAH509Omgg0BfKLY+ViRlzrvYOTWR0FMS2SQTwzumrw==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.0" + } + }, "node_modules/ts-invariant": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.7.3.tgz", - "integrity": "sha512-UWDDeovyUTIMWj+45g5nhnl+8oo+GhxL5leTaHn5c8FkQWfh8v66gccLd2/YzVmV5hoQUjCEjhrXnQqVDJdvKA==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", + "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", "dependencies": { "tslib": "^2.1.0" }, @@ -19021,67 +21025,26 @@ } }, "node_modules/ts-invariant/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "node_modules/ts-is-defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ts-is-defined/-/ts-is-defined-1.0.0.tgz", - "integrity": "sha512-HmzqN8xWETXnfpXyUqMf5nvcZszn9aTNjxVIJ6R2aNNg14oLo3PCi9IRhsv+vg2C7TI90M7PyjBOrg4f6/nupA==", - "dev": true, - "dependencies": { - "ts-tiny-invariant": "0.0.3" - } - }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/ts-tiny-invariant": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/ts-tiny-invariant/-/ts-tiny-invariant-0.0.3.tgz", - "integrity": "sha512-EiaBUsUta7PPzVKpvZurcSDgaSkymxwiUc2rhX6Wu30bws2maipT6ihbEY072dU9lz6/FoFWEc6psXdlo0xqtg==", - "dev": true + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", - "minimist": "^1.2.0", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -19096,35 +21059,20 @@ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, "node_modules/tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { "tslib": "^1.8.1" }, "engines": { "node": ">= 6" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" }, - "engines": { - "node": "*" + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -19167,6 +21115,20 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -19177,28 +21139,29 @@ } }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -19206,13 +21169,10 @@ } }, "node_modules/undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "dependencies": { - "debug": "^2.2.0" - } + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true }, "node_modules/unfetch": { "version": "4.2.0", @@ -19220,40 +21180,40 @@ "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "dependencies": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" }, "engines": { "node": ">=4" } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "dev": true, "engines": { "node": ">=4" @@ -19274,27 +21234,6 @@ "node": ">=0.10.0" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -19357,108 +21296,34 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dev": true, - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "color-name": "~1.1.4" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/update-notifier/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "bin": { + "update-browserslist-db": "cli.js" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { @@ -19476,44 +21341,16 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, "node_modules/url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -19524,15 +21361,18 @@ } }, "node_modules/use-callback-ref": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", - "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, "engines": { - "node": ">=8.5.0" + "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -19540,43 +21380,43 @@ } } }, + "node_modules/use-callback-ref/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/use-sidecar": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz", - "integrity": "sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", "dependencies": { "detect-node-es": "^1.1.0", - "tslib": "^1.9.3" + "tslib": "^2.0.0" }, "engines": { - "node": ">=8.5.0" + "node": ">=10" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, + "node_modules/use-sidecar/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "node_modules/util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", @@ -19639,15 +21479,10 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, "node_modules/value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", + "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", "dev": true, "engines": { "node": ">=12" @@ -19661,20 +21496,6 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -19705,18 +21526,10 @@ "makeerror": "1.0.x" } }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/watchpack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.0.0.tgz", - "integrity": "sha512-xSdCxxYZWNk3VK13bZRYhsQpfa8Vg63zXG+3pyU8ouqSLRCv4IGXIp9Kr226q6GBkGRlZrST2wwKtjfKz2m7Cg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -19754,62 +21567,70 @@ } }, "node_modules/webpack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.2.0.tgz", - "integrity": "sha512-evtOjOJQq3zaHJIWsJjM4TGtNHtSrNVAIyQ+tdPW/fRd+4PLGbUG6S3xt+N4+QwDBOaCVd0xCWiHd4R6lWO5DQ==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.45", - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^8.0.4", - "browserslist": "^4.14.5", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "dev": true, + "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.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.3.0", - "eslint-scope": "^5.1.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.1.0", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "pkg-dir": "^4.2.0", - "schema-utils": "^3.0.0", - "tapable": "^2.0.0", - "terser-webpack-plugin": "^5.0.0", - "watchpack": "^2.0.0", - "webpack-sources": "^2.0.1" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, "node_modules/webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", + "cross-spawn": "^7.0.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", "webpack-merge": "^5.7.3" }, "bin": { @@ -19817,6 +21638,27 @@ }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, "node_modules/webpack-cli/node_modules/commander": { @@ -19828,83 +21670,6 @@ "node": ">= 10" } }, - "node_modules/webpack-cli/node_modules/execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webpack-cli/node_modules/get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/webpack-cli/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/webpack-cli/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-cli/node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/webpack-config-utils": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/webpack-config-utils/-/webpack-config-utils-2.3.1.tgz", @@ -19914,14 +21679,6 @@ "webpack-combine-loaders": "2.0.4" } }, - "node_modules/webpack-config-utils/node_modules/qs": { - "version": "6.5.2", - "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/webpack-config-utils/node_modules/webpack-combine-loaders": { "version": "2.0.4", "integrity": "sha1-J4FNUrgyntZWW+OQCarHY2Hn4iw=", @@ -19931,609 +21688,698 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" + "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-middleware/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, - "bin": { - "mime": "cli.js" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=4.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/webpack-dev-middleware/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "fast-deep-equal": "^3.1.3" }, - "bin": { - "mkdirp": "bin/cmd.js" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", - "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "dependencies": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.7", - "semver": "^6.3.0", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", "serve-index": "^1.9.1", - "sockjs": "0.3.20", - "sockjs-client": "1.4.0", + "sockjs": "^0.3.24", "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 6.11.5" - } - }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } } }, - "node_modules/webpack-dev-server/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "dependencies": { - "remove-trailing-separator": "^1.0.1" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/webpack-dev-server/node_modules/braces/node_modules/extend-shallow": { + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 10" } }, - "node_modules/webpack-dev-server/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "glob": "^7.1.3" }, - "optionalDependencies": { - "fsevents": "^1.2.7" + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webpack-dev-server/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/webpack-dev-server/node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/webpack-merge": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", + "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", "dev": true, "dependencies": { - "ansi-regex": "^4.1.0" + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=10.0.0" } }, - "node_modules/webpack-dev-server/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">=10.13.0" } }, - "node_modules/webpack-dev-server/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/webpack-dev-server/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "node_modules/webpack/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/webpack-dev-server/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 4.0" - } + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/webpack-dev-server/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "dependencies": { - "is-extglob": "^2.1.0" + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.8.0" } }, - "node_modules/webpack-dev-server/node_modules/import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true, - "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, "engines": { - "node": ">=6" + "node": ">=0.8.0" } }, - "node_modules/webpack-dev-server/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "iconv-lite": "0.4.24" } }, - "node_modules/webpack-dev-server/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", + "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/webpack-dev-server/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/webpack-dev-server/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "node_modules/webpack-dev-server/node_modules/resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "dependencies": { - "resolve-from": "^3.0.0" + "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" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/webpack-dev-server/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack-dev-server/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=8" } }, - "node_modules/webpack-dev-server/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/webpack-dev-server/node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/webpack-dev-server/node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "ansi-regex": "^4.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=6" + "node": ">=7.0.0" } }, - "node_modules/webpack-dev-server/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/webpack-dev-server/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=6" + "node": ">=7.0.0" } }, - "node_modules/webpack-dev-server/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xss": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", + "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", "dev": true, "dependencies": { - "ansi-regex": "^4.1.0" + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" }, "engines": { - "node": ">=6" + "node": ">= 0.10.0" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0" + "node_modules/xss/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "engines": { + "node": ">= 6" } }, - "node_modules/webpack-dev-server/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", + "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": "^3.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" } }, - "node_modules/webpack-dev-server/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-log/node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true, "engines": { "node": ">=6" } }, - "node_modules/webpack-log/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, "engines": { - "node": ">=10.0.0" + "node": ">=6" } }, - "node_modules/webpack/node_modules/find-up": { + "node_modules/yargs/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", @@ -20546,7 +22392,7 @@ "node": ">=8" } }, - "node_modules/webpack/node_modules/locate-path": { + "node_modules/yargs/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", @@ -20558,7 +22404,7 @@ "node": ">=8" } }, - "node_modules/webpack/node_modules/p-locate": { + "node_modules/yargs/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", @@ -20570,7 +22416,7 @@ "node": ">=8" } }, - "node_modules/webpack/node_modules/path-exists": { + "node_modules/yargs/node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", @@ -20579,2383 +22425,2611 @@ "node": ">=8" } }, - "node_modules/webpack/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" + "node_modules/yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - } + "node_modules/zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + } + }, + "dependencies": { + "@adobe/css-tools": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", + "dev": true }, - "node_modules/webpack/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "devOptional": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "node_modules/webpack/node_modules/tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", - "dev": true, - "engines": { - "node": ">=6" + "@apollo/client": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.6.9.tgz", + "integrity": "sha512-Y1yu8qa2YeaCUBVuw08x8NHenFi0sw2I3KCu7Kw9mDSu86HmmtHJkCAifKVrN2iPgDTW/BbP3EpSV8/EQCcxZA==", + "requires": { + "@graphql-typed-document-node/core": "^3.1.1", + "@wry/context": "^0.6.0", + "@wry/equality": "^0.5.0", + "@wry/trie": "^0.3.0", + "graphql-tag": "^2.12.6", + "hoist-non-react-statics": "^3.3.2", + "optimism": "^0.16.1", + "prop-types": "^15.7.2", + "symbol-observable": "^4.0.0", + "ts-invariant": "^0.10.3", + "tslib": "^2.3.0", + "zen-observable-ts": "^1.2.5" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "zen-observable-ts": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz", + "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==", + "requires": { + "zen-observable": "0.8.15" + } + } } }, - "node_modules/webpack/node_modules/webpack-sources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.0.1.tgz", - "integrity": "sha512-A9oYz7ANQBK5EN19rUXbvNgfdfZf5U2gP0769OXsj9CvYkCR6OHOsd6OKyEy4H38GGxpsQPKIL83NC64QY6Xmw==", + "@apollo/protobufjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", + "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", "dev": true, - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" + "requires": { + "@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/long": "^4.0.0", + "long": "^4.0.0" } }, - "node_modules/websocket-driver": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", - "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", "dev": true, - "dependencies": { - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.6.0" + "requires": { + "@apollo/protobufjs": "1.2.7" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "@apollo/utils.dropunuseddefinitions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-1.1.0.tgz", + "integrity": "sha512-jU1XjMr6ec9pPoL+BFWzEPW7VHHulVdGKMkPAMiCigpVIT11VmCbnij0bWob8uS3ODJ65tZLYKAh/55vLw2rbg==", "dev": true, - "engines": { - "node": ">=0.8.0" - } + "requires": {} }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "@apollo/utils.keyvaluecache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz", + "integrity": "sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg==", "dev": true, + "requires": { + "@apollo/utils.logger": "^1.0.0", + "lru-cache": "7.10.1 - 7.13.1" + }, "dependencies": { - "iconv-lite": "0.4.24" + "lru-cache": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz", + "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==", + "dev": true + } } }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "@apollo/utils.logger": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-1.0.1.tgz", + "integrity": "sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA==", "dev": true }, - "node_modules/whatwg-url": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", - "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", + "@apollo/utils.printwithreducedwhitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-1.1.0.tgz", + "integrity": "sha512-GfFSkAv3n1toDZ4V6u2d7L4xMwLA+lv+6hqXicMN9KELSJ9yy9RzuEXaX73c/Ry+GzRsBy/fdSUGayGqdHfT2Q==", "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } + "requires": {} }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "@apollo/utils.removealiases": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-1.0.0.tgz", + "integrity": "sha512-6cM8sEOJW2LaGjL/0vHV0GtRaSekrPQR4DiywaApQlL9EdROASZU5PsQibe2MWeZCOhNrPRuHh4wDMwPsWTn8A==", "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "requires": {} + }, + "@apollo/utils.sortast": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-1.1.0.tgz", + "integrity": "sha512-VPlTsmUnOwzPK5yGZENN069y6uUHgeiSlpEhRnLFYwYNoJHsuJq2vXVwIaSmts015WTPa2fpz1inkLYByeuRQA==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "@apollo/utils.stripsensitiveliterals": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-1.2.0.tgz", + "integrity": "sha512-E41rDUzkz/cdikM5147d8nfCFVKovXxKBcjvLEQ7bjZm/cg9zEcXvS6vFY8ugTubI3fn6zoqo0CyU8zT+BGP9w==", "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "requires": {} + }, + "@apollo/utils.usagereporting": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-1.0.1.tgz", + "integrity": "sha512-6dk+0hZlnDbahDBB2mP/PZ5ybrtCJdLMbeNJD+TJpKyZmSY6bA3SjI8Cr2EM9QA+AdziywuWg+SgbWUF3/zQqQ==", + "dev": true, + "requires": { + "@apollo/usage-reporting-protobuf": "^4.0.0", + "@apollo/utils.dropunuseddefinitions": "^1.1.0", + "@apollo/utils.printwithreducedwhitespace": "^1.1.0", + "@apollo/utils.removealiases": "1.0.0", + "@apollo/utils.sortast": "^1.1.0", + "@apollo/utils.stripsensitiveliterals": "^1.2.0" } }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "@apollographql/apollo-tools": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz", + "integrity": "sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw==", + "dev": true, + "requires": {} }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "@apollographql/graphql-playground-html": { + "version": "1.6.29", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", + "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", + "dev": true, + "requires": { + "xss": "^1.0.8" + } + }, + "@babel/cli": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.18.10.tgz", + "integrity": "sha512-dLvWH+ZDFAkd2jPBSghrsFBuXrREvFwjpDycXbmUoeochqKYe4zNSLEJYErpLg8dvxvZYe79/MkN461XCwpnGw==", "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.8", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, "dependencies": { - "string-width": "^4.0.0" + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "requires": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "devOptional": true + }, + "@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "devOptional": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, - "engines": { - "node": ">=8" + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "devOptional": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "devOptional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "devOptional": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true + } } }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true + "@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "requires": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + } + } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "requires": { + "@babel/types": "^7.18.6" } }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "devOptional": true, + "requires": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, - "engines": { - "node": ">=8" + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "devOptional": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "devOptional": true + } } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@babel/helper-create-class-features-plugin": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "@babel/helper-create-regexp-features-plugin": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", + "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "@babel/helper-define-polyfill-provider": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", + "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" + "requires": { + "@babel/types": "^7.18.6" } }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, - "node_modules/write/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "requires": { + "@babel/types": "^7.22.5" } }, - "node_modules/ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", "dev": true, - "engines": { - "node": ">=8.3.0" + "requires": { + "@babel/types": "^7.18.9" } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "requires": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "devOptional": true, + "requires": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@babel/types": "^7.18.6" } }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", "dev": true }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } }, - "node_modules/xss": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz", - "integrity": "sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ==", + "@babel/helper-replace-supers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", + "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", "dev": true, - "dependencies": { - "commander": "^2.20.3", - "cssfilter": "0.0.10" - }, - "bin": { - "xss": "bin/xss" - }, - "engines": { - "node": ">= 0.10.0" + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" } }, - "node_modules/xss/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "devOptional": true, + "requires": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + } }, - "node_modules/y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", - "dev": true + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } }, - "node_modules/yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", - "engines": { - "node": ">= 6" + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "devOptional": true + }, + "@babel/helper-wrap-function": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" } }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "devOptional": true, + "requires": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + } + }, + "@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, - "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.2" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "requires": { + "@babel/types": "^7.28.5" + } + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", "dev": true, - "engines": { - "node": ">=6" + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" } }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" } }, - "node_modules/yargs/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/yargs/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, - "node_modules/yargs/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", "dev": true, - "engines": { - "node": ">=8" + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", "dev": true, - "engines": { - "node": ">=6" + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, - "node_modules/yup": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.29.3.tgz", - "integrity": "sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==", - "dependencies": { - "@babel/runtime": "^7.10.5", - "fn-name": "~3.0.0", - "lodash": "^4.17.15", - "lodash-es": "^4.17.11", - "property-expr": "^2.0.2", - "synchronous-promise": "^2.0.13", - "toposort": "^2.0.2" - }, - "engines": { - "node": ">=10" + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" } }, - "node_modules/zen-observable": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } }, - "node_modules/zen-observable-ts": { - "version": "0.8.21", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz", - "integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==", + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "dev": true, - "dependencies": { - "tslib": "^1.9.3", - "zen-observable": "^0.8.0" + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } - } - }, - "dependencies": { - "@apollo/client": { - "version": "3.3.13", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.3.13.tgz", - "integrity": "sha512-usiVmXiOq0J/GpyIOIhlF8ItHpiPJObC7zoxLYPoOYj3G3OB0hCIcUKs3aTJ3ATW7u8QxvYgRaJg72NN7E1WOg==", + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, "requires": { - "@graphql-typed-document-node/core": "^3.0.0", - "@types/zen-observable": "^0.8.0", - "@wry/context": "^0.6.0", - "@wry/equality": "^0.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graphql-tag": "^2.12.0", - "hoist-non-react-statics": "^3.3.2", - "optimism": "^0.15.0", - "prop-types": "^15.7.2", - "symbol-observable": "^2.0.0", - "ts-invariant": "^0.7.0", - "tslib": "^1.10.0", - "zen-observable": "^0.8.14" - }, - "dependencies": { - "graphql-tag": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.3.tgz", - "integrity": "sha512-5wJMjSvj30yzdciEuk9dPuUBUR56AqDi3xncoYQl1i42pGdSqOJrJsdb/rz5BDoy+qoGvQwABcBeF0xXY3TrKw==", - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - } - } - } + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, - "@apollo/protobufjs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", - "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", "dev": true, "requires": { - "@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/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" } }, - "@apollographql/apollo-tools": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.1.tgz", - "integrity": "sha512-ZII+/xUFfb9ezDU2gad114+zScxVFMVlZ91f8fGApMzlS1kkqoyLnC4AJaQ1Ya/X+b63I20B4Gd+eCL8QuB4sA==", - "dev": true + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } }, - "@apollographql/graphql-playground-html": { - "version": "1.6.27", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.27.tgz", - "integrity": "sha512-tea2LweZvn6y6xFV11K0KC8ETjmm52mQrW+ezgB2O/aTQf8JGyFmMcRPFgUaQZeHbWdm8iisDC6EjOKsXu0nfw==", + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", "dev": true, "requires": { - "xss": "^1.0.8" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, - "@apollographql/graphql-upload-8-fork": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-upload-8-fork/-/graphql-upload-8-fork-8.1.3.tgz", - "integrity": "sha512-ssOPUT7euLqDXcdVv3Qs4LoL4BPtfermW1IOouaqEmj36TpHYDmYDIbKoSQxikd9vtMumFnP87OybH7sC9fJ6g==", + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "dev": true, "requires": { - "@types/express": "*", - "@types/fs-capacitor": "*", - "@types/koa": "*", - "busboy": "^0.3.1", - "fs-capacitor": "^2.0.4", - "http-errors": "^1.7.3", - "object-path": "^0.11.4" - }, - "dependencies": { - "http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - } + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@ardatan/aggregate-error": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz", - "integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==", + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", "dev": true, "requires": { - "tslib": "~2.0.1" - }, - "dependencies": { - "tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", - "dev": true - } + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, - "@babel/cli": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.16.tgz", - "integrity": "sha512-cL9tllhqvsQ6r1+d9Invf7nNXg/3BlfL1vvvL/AdH9fZ2l5j0CeBcoq6UjsqHpvyN1v5nXSZgqJZoGeK+ZOAbw==", + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "dev": true, "requires": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==" - }, - "@babel/core": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz", - "integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/generator": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz", - "integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==", + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "requires": { - "@babel/types": "^7.14.1", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/helper-plugin-utils": "^7.12.13" } }, - "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } + "@babel/helper-plugin-utils": "^7.8.3" } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz", - "integrity": "sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==", + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/helper-explode-assignable-expression": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", - "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/helper-hoist-variables": { + "@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/helper-module-transforms": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz", - "integrity": "sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==", + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/helper-plugin-utils": { + "@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==" - }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "requires": { - "lodash": "^4.17.19" + "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/helper-remap-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", - "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/types": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" } }, - "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "@babel/plugin-transform-block-scoping": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "requires": { - "@babel/types": "^7.12.13" + "@babel/plugin-transform-classes": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", + "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" - }, - "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" - }, - "@babel/helper-wrap-function": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", - "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "@babel/plugin-transform-destructuring": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", + "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", + "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/parser": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz", - "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==" + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", - "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", - "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", - "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", - "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", + "@babel/plugin-transform-modules-amd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", - "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "@babel/plugin-transform-modules-commonjs": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", - "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", + "@babel/plugin-transform-modules-systemjs": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", + "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-identifier": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", - "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", + "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", - "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" } }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", - "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "@babel/plugin-transform-react-jsx": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.10.tgz", + "integrity": "sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.18.10" } }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/plugin-transform-react-jsx": "^7.18.6" } }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" } }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", + "@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "@babel/plugin-transform-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", + "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" } }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", - "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@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/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", - "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" } }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", - "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", - "dev": true, + "@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "regenerator-runtime": "^0.14.0" } }, - "@babel/plugin-transform-block-scoping": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", - "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", - "dev": true, + "@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" } }, - "@babel/plugin-transform-classes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", - "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, - "@babel/plugin-transform-computed-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", - "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", - "dev": true, + "@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, - "@babel/plugin-transform-destructuring": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", - "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", - "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", - "dev": true, + "@chakra-ui/accordion": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.3.1.tgz", + "integrity": "sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" } }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", - "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", - "dev": true, + "@chakra-ui/alert": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.2.2.tgz", + "integrity": "sha512-jHg4LYMRNOJH830ViLuicjb3F+v6iriE/2G5T+Sd0Hna04nukNJ1MxUmBPE+vI22me2dIflfelu2v9wdB6Pojw==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", - "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } + "@chakra-ui/anatomy": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==" }, - "@babel/plugin-transform-for-of": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", - "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", - "dev": true, + "@chakra-ui/avatar": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/image": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-function-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", - "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", - "dev": true, + "@chakra-ui/breadcrumb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", - "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", - "dev": true, + "@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", - "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", - "dev": true, + "@chakra-ui/button": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" } }, - "@babel/plugin-transform-modules-amd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", - "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", - "dev": true, + "@chakra-ui/card": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.2.0.tgz", + "integrity": "sha512-xUB/k5MURj4CtPAhdSoXZidUbm8j3hci9vnc+eZJVDqhDOShNlD6QeniQNRPRys4lWAQLCbFcrwL29C8naDi6g==", "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", - "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.12.1", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@chakra-ui/checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.3.2.tgz", + "integrity": "sha512-85g38JIXMEv6M+AcyIGLh7igNtfpAN6KGQFYxY9tBj0eWvWk4NKQxvqqyVta0bSAyIl1rixNIIezNpNWk2iO4g==", + "requires": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.2.0", + "@zag-js/focus-visible": "0.16.0" } }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", - "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", - "dev": true, + "@chakra-ui/clickable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-identifier": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", - "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", - "dev": true, + "@chakra-ui/close-button": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/icon": "3.2.0" } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", - "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", - "dev": true, + "@chakra-ui/color-mode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", + "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, - "@babel/plugin-transform-new-target": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", - "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", - "dev": true, + "@chakra-ui/control-box": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.1.0.tgz", + "integrity": "sha512-gVrRDyXFdMd8E7rulL0SKeoljkLQiPITFnsyMO8EFHNZ+AHt5wK4LIguYVEq88APqAGZGfHFWXr79RYrNiE3Mg==", + "requires": {} + }, + "@chakra-ui/counter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-object-super": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", - "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", - "dev": true, + "@chakra-ui/css-reset": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.3.0.tgz", + "integrity": "sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==", + "requires": {} + }, + "@chakra-ui/descendant": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" } }, - "@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", - "dev": true, + "@chakra-ui/dom-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.1.0.tgz", + "integrity": "sha512-ZmF2qRa1QZ0CMLU8M1zCfmw29DmPNtfjR9iTo74U5FPr3i1aoAh7fbJ4qAlZ197Xw9eAW28tvzQuoVWeL5C7fQ==" + }, + "@chakra-ui/editable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-property-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", - "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", - "dev": true, + "@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==" + }, + "@chakra-ui/focus-lock": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.1.0.tgz", + "integrity": "sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" } }, - "@babel/plugin-transform-react-display-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz", - "integrity": "sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA==", - "dev": true, + "@chakra-ui/form-control": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", + "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-react-jsx": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz", - "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==", - "dev": true, + "@chakra-ui/hooks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/types": "^7.13.12" + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "3.0.3", + "copy-to-clipboard": "3.3.3" }, "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dev": true, + "@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "requires": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "requires": { + "@types/lodash": "*" + } + }, + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", "requires": { - "@babel/types": "^7.12.13" + "tslib": "2.4.0" } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" } } }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz", - "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==", - "dev": true, + "@chakra-ui/icon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", "requires": { - "@babel/plugin-transform-react-jsx": "^7.12.17" + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", - "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", - "dev": true, + "@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/icon": "3.2.0" } }, - "@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", - "dev": true, + "@chakra-ui/image": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", "requires": { - "regenerator-transform": "^0.14.2" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", - "dev": true, + "@chakra-ui/input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-runtime": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", - "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", - "dev": true, + "@chakra-ui/layout": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.3.1.tgz", + "integrity": "sha512-nXuZ6WRbq0WdgnRgLw+QuxWAHuhDtVX8ElWqcTK+cSMFg/52eVP47czYBE5F35YhnoW2XBwfNoNgZ7+e8Z01Rg==", "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "resolve": "^1.8.1", - "semver": "^5.5.1" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", - "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==" }, - "@babel/plugin-transform-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", - "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" - } + "@chakra-ui/live-region": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.1.0.tgz", + "integrity": "sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==", + "requires": {} }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", - "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", - "dev": true, + "@chakra-ui/media-query": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.3.0.tgz", + "integrity": "sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/plugin-transform-template-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", - "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/menu": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.2.1.tgz", + "integrity": "sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==", + "requires": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-outside-click": "2.2.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" } }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", - "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", - "dev": true, + "@chakra-ui/modal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0", + "aria-hidden": "^1.2.3", + "react-remove-scroll": "^2.5.6" } }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", - "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } + "@chakra-ui/number-input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", + "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", + "requires": { + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-interval": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==" }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", - "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", - "dev": true, + "@chakra-ui/object-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==" + }, + "@chakra-ui/pin-input": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.1.0.tgz", + "integrity": "sha512-x4vBqLStDxJFMt+jdAHHS8jbh294O53CPQJoL4g228P513rHylV/uPscYUHrVJXRxsHfRztQO9k45jjTYaPRMw==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/preset-env": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", - "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.12.7", - "@babel/helper-compilation-targets": "^7.12.5", - "@babel/helper-module-imports": "^7.12.5", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.1", - "@babel/plugin-proposal-json-strings": "^7.12.1", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.7", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.7", - "@babel/plugin-proposal-private-methods": "^7.12.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.1", - "@babel/plugin-transform-arrow-functions": "^7.12.1", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.11", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-computed-properties": "^7.12.1", - "@babel/plugin-transform-destructuring": "^7.12.1", - "@babel/plugin-transform-dotall-regex": "^7.12.1", - "@babel/plugin-transform-duplicate-keys": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-function-name": "^7.12.1", - "@babel/plugin-transform-literals": "^7.12.1", - "@babel/plugin-transform-member-expression-literals": "^7.12.1", - "@babel/plugin-transform-modules-amd": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-modules-systemjs": "^7.12.1", - "@babel/plugin-transform-modules-umd": "^7.12.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", - "@babel/plugin-transform-new-target": "^7.12.1", - "@babel/plugin-transform-object-super": "^7.12.1", - "@babel/plugin-transform-parameters": "^7.12.1", - "@babel/plugin-transform-property-literals": "^7.12.1", - "@babel/plugin-transform-regenerator": "^7.12.1", - "@babel/plugin-transform-reserved-words": "^7.12.1", - "@babel/plugin-transform-shorthand-properties": "^7.12.1", - "@babel/plugin-transform-spread": "^7.12.1", - "@babel/plugin-transform-sticky-regex": "^7.12.7", - "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.10", - "@babel/plugin-transform-unicode-escapes": "^7.12.1", - "@babel/plugin-transform-unicode-regex": "^7.12.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.11", - "core-js-compat": "^3.8.0", - "semver": "^5.5.0" + "@chakra-ui/popover": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.2.1.tgz", + "integrity": "sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==", + "requires": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dev": true, + "@chakra-ui/popper": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@popperjs/core": "^2.9.3" } }, - "@babel/preset-react": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz", - "integrity": "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA==", - "dev": true, + "@chakra-ui/portal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-transform-react-display-name": "^7.12.13", - "@babel/plugin-transform-react-jsx": "^7.13.12", - "@babel/plugin-transform-react-jsx-development": "^7.12.17", - "@babel/plugin-transform-react-pure-annotations": "^7.12.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, - "@babel/runtime": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", - "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "@chakra-ui/progress": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", "requires": { - "regenerator-runtime": "^0.13.4" + "@chakra-ui/react-context": "2.1.0" } }, - "@babel/runtime-corejs3": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", - "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", - "dev": true, + "@chakra-ui/provider": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.4.2.tgz", + "integrity": "sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==", "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/utils": "2.0.15" + }, + "dependencies": { + "@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "requires": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "requires": { + "@types/lodash": "*" + } + }, + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "requires": { + "tslib": "2.4.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "@chakra-ui/radio": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-n10M46wJrMGbonaghvSRnZ9ToTv/q76Szz284gv4QUWvyljQACcGrXIONUnQ3BIwbOfkRqSk7Xl/JgZtVfll+w==", "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.16.0" } }, - "@babel/traverse": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz", - "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.14.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" + "@chakra-ui/react": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.8.2.tgz", + "integrity": "sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==", + "requires": { + "@chakra-ui/accordion": "2.3.1", + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/avatar": "2.3.0", + "@chakra-ui/breadcrumb": "2.2.0", + "@chakra-ui/button": "2.1.0", + "@chakra-ui/card": "2.2.0", + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/control-box": "2.1.0", + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/editable": "3.1.0", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/hooks": "2.2.1", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/image": "2.1.0", + "@chakra-ui/input": "2.1.2", + "@chakra-ui/layout": "2.3.1", + "@chakra-ui/live-region": "2.1.0", + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/menu": "2.2.1", + "@chakra-ui/modal": "2.3.1", + "@chakra-ui/number-input": "2.1.2", + "@chakra-ui/pin-input": "2.1.0", + "@chakra-ui/popover": "2.2.1", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/progress": "2.2.0", + "@chakra-ui/provider": "2.4.2", + "@chakra-ui/radio": "2.1.2", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/select": "2.1.2", + "@chakra-ui/skeleton": "2.1.0", + "@chakra-ui/skip-nav": "2.1.0", + "@chakra-ui/slider": "2.1.0", + "@chakra-ui/spinner": "2.1.0", + "@chakra-ui/stat": "2.1.1", + "@chakra-ui/stepper": "2.3.1", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/switch": "2.1.2", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/table": "2.1.0", + "@chakra-ui/tabs": "3.0.0", + "@chakra-ui/tag": "3.1.1", + "@chakra-ui/textarea": "2.1.2", + "@chakra-ui/theme": "3.3.1", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/toast": "7.0.2", + "@chakra-ui/tooltip": "2.3.1", + "@chakra-ui/transition": "2.1.0", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.2.0" + }, + "dependencies": { + "@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "requires": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "requires": { + "@types/lodash": "*" + } + }, + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "requires": { + "tslib": "2.4.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" } } }, - "@babel/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz", - "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==", + "@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "requires": {} + }, + "@chakra-ui/react-context": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", + "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", + "requires": {} + }, + "@chakra-ui/react-env": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.1.0.tgz", + "integrity": "sha512-Vr96GV2LNBth3+IKzr/rq1IcnkXv+MLmwjQH6C8BRtn3sNskgDFD5vLkVXcEhagzZMCh8FR3V/bzZPojBOyNhw==", "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", + "requires": {} }, - "@chakra-ui/accordion": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-1.3.4.tgz", - "integrity": "sha512-X+o68wcMkm07yWGjZz69rRke6W0zsD1eEG8uBs7iFy+q0sc1n5LiHNO/1L6s6CyBo6omI31RS/fbLD9OXJVD1g==", + "@chakra-ui/react-use-animation-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.1.0.tgz", + "integrity": "sha512-CFZkQU3gmDBwhqy0vC1ryf90BVHxVN8cTLpSyCpdmExUEtSEInSCGMydj2fvn7QXsz/za8JNdO2xxgJwxpLMtg==", "requires": { - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" } }, - "@chakra-ui/alert": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-1.2.6.tgz", - "integrity": "sha512-aq2hVHQFe3sFHYWDj+3HRVTKOqWlWwpm/FFihPVNoYteLKje8f71n3VN3rhDaFY15tFDXq9Uv3qTdMK55KXGlg==", + "@chakra-ui/react-use-callback-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", + "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", + "requires": {} + }, + "@chakra-ui/react-use-controllable-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.1.0.tgz", + "integrity": "sha512-QR/8fKNokxZUs4PfxjXuwl0fj/d71WPrmLJvEpCTkHjnzu7LnYvzoe2wB867IdooQJL0G1zBxl0Dq+6W1P3jpg==", "requires": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/avatar": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-1.2.7.tgz", - "integrity": "sha512-WwtHDEmnSglBKOkxQHRu8tUtRTKu+vn35JlO6QVP+Mb5SPX0vFns3F38dohVr2s1wGUiMVMq/bt0JNCG5fFzhQ==", + "@chakra-ui/react-use-disclosure": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.1.0.tgz", + "integrity": "sha512-Ax4pmxA9LBGMyEZJhhUZobg9C0t3qFE4jVF1tGBsrLDcdBeLR9fwOogIPY9Hf0/wqSlAryAimICbr5hkpa5GSw==", "requires": { - "@chakra-ui/image": "1.0.17", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/breadcrumb": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-1.2.7.tgz", - "integrity": "sha512-gJVigaLRIkRCNBgH8B36fOFCgGIKErZOutchhIOCiycWnIStaGiZ7XpQIbuXCWHcLtWG3+YRL4pupx7mOPoc3w==", + "@chakra-ui/react-use-event-listener": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", + "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", "requires": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/button": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-1.4.1.tgz", - "integrity": "sha512-KnxG0buRMdM5KM1p00UozZ9KmZ22RKWUHvJrqtfi2Qxcj6FaEgS3nTXInLRpMIQ5xc83O07mio+pZ1j4zoRrbw==", + "@chakra-ui/react-use-focus-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.1.0.tgz", + "integrity": "sha512-xzVboNy7J64xveLcxTIJ3jv+lUJKDwRM7Szwn9tNzUIPD94O3qwjV7DDCUzN2490nSYDF4OBMt/wuDBtaR3kUQ==", "requires": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/spinner": "1.1.11", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" } }, - "@chakra-ui/checkbox": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-1.5.4.tgz", - "integrity": "sha512-exEfDZZK2IQjT4DpTYynC7wdUGWxBTo+iYfTmA/DOvcTW9RqETgYSJteRUTZdFgA3AptH1XN/PuAj/ucIsQ9VA==", + "@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.1.0.tgz", + "integrity": "sha512-2jzrUZ+aiCG/cfanrolsnSMDykCAbv9EK/4iUyZno6BYb3vziucmvgKuoXbMPAzWNtwUwtuMhkby8rc61Ue+Lg==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "@chakra-ui/react-use-event-listener": "2.1.0" } }, - "@chakra-ui/clickable": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-1.1.6.tgz", - "integrity": "sha512-wCA/QKXwJaB6t6DRfIk8tKRBkHMmgG3aqXD9/KusXb+3OGDExuxrcO/nBkpTwZJ0+y0FPADpOduLupnrHQ4KNw==", + "@chakra-ui/react-use-interval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", + "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", "requires": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/close-button": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-1.1.10.tgz", - "integrity": "sha512-DgjPZlqt2lixmLfnWaeqUQwGzRW3Ld1UNncjMzVUhTFxyfgSOCRLTQP4Hj4NWXilK3SuiPtxrtxAzm1sdYRfLg==", + "@chakra-ui/react-use-latest-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.1.0.tgz", + "integrity": "sha512-m0kxuIYqoYB0va9Z2aW4xP/5b7BzlDeWwyXCH6QpT2PpW3/281L3hLCm1G0eOUcdVlayqrQqOeD6Mglq+5/xoQ==", + "requires": {} + }, + "@chakra-ui/react-use-merge-refs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", + "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", + "requires": {} + }, + "@chakra-ui/react-use-outside-click": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.2.0.tgz", + "integrity": "sha512-PNX+s/JEaMneijbgAM4iFL+f3m1ga9+6QK0E5Yh4s8KZJQ/bLwZzdhMz8J/+mL+XEXQ5J0N8ivZN28B82N1kNw==", "requires": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/color-mode": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-1.1.10.tgz", - "integrity": "sha512-fMI4yeaWjlDwM9gsGpD4G23j/7aVL7UQcZmPnyTsyPXWM7Y51CO7VF8Nr7WCeq2l0axjhVqMs+HveL4biM+kGw==", + "@chakra-ui/react-use-pan-event": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.1.0.tgz", + "integrity": "sha512-xmL2qOHiXqfcj0q7ZK5s9UjTh4Gz0/gL9jcWPA6GVf+A0Od5imEDa/Vz+533yQKWiNSm1QGrIj0eJAokc7O4fg==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "framesync": "6.1.2" + }, + "dependencies": { + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "requires": { + "tslib": "2.4.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, - "@chakra-ui/control-box": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-1.0.14.tgz", - "integrity": "sha512-BJJQnOy0C6gDH1sbQTRYflaWdc0h3IafcGAD0d2WGYVscMicAiNd/+6qGfqivrCESpghz4pfDcNE96UIFUYvHg==", + "@chakra-ui/react-use-previous": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.1.0.tgz", + "integrity": "sha512-pjxGwue1hX8AFcmjZ2XfrQtIJgqbTF3Qs1Dy3d1krC77dEsiCUbQ9GzOBfDc8pfd60DrB5N2tg5JyHbypqh0Sg==", + "requires": {} + }, + "@chakra-ui/react-use-safe-layout-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", + "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", + "requires": {} + }, + "@chakra-ui/react-use-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.1.0.tgz", + "integrity": "sha512-tbLqrQhbnqOjzTaMlYytp7wY8BW1JpL78iG7Ru1DlV4EWGiAmXFGvtnEt9HftU0NJ0aJyjgymkxfVGI55/1Z4A==", "requires": { - "@chakra-ui/utils": "1.8.1" + "@zag-js/element-size": "0.10.5" } }, - "@chakra-ui/counter": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-1.1.7.tgz", - "integrity": "sha512-RrlbFg8u3UNcqPm7SLyJGLeqPnFuRqccXXL98Udy5wLhEe1maI6mUPu0bZHTm0VJ1AEdiVzbql0qH8HLneMiGg==", + "@chakra-ui/react-use-timeout": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.1.0.tgz", + "integrity": "sha512-cFN0sobKMM9hXUhyCofx3/Mjlzah6ADaEl/AXl5Y+GawB5rgedgAcu2ErAgarEkwvsKdP6c68CKjQ9dmTQlJxQ==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/css-reset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.0.0.tgz", - "integrity": "sha512-UaPsImGHvCgFO3ayp6Ugafu2/3/EG8wlW/8Y9Ihfk1UFv8cpV+3BfWKmuZ7IcmxcBL9dkP6E8p3/M1T0FB92hg==", + "@chakra-ui/react-use-update-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", + "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", "requires": {} }, - "@chakra-ui/descendant": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-2.0.1.tgz", - "integrity": "sha512-TeYp94iOhu5Gs2oVzewJaep0qft/JKMKfmcf4PGgzJF+h6TWZm6NGohk6Jq7JOh+y0rExa1ulknIgnMzFx5xaA==", + "@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", "requires": { - "@chakra-ui/react-utils": "^1.1.2" + "@chakra-ui/utils": "2.0.15" + }, + "dependencies": { + "@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "requires": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "requires": { + "@types/lodash": "*" + } + }, + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "requires": { + "tslib": "2.4.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, - "@chakra-ui/editable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-1.2.7.tgz", - "integrity": "sha512-wmS5eGNw4ACX+kMEPxV97B6DEMJhGmvsUpdJAA8HDbDdcZNZk93Zkuog10X1cvXaddNCpDkFaa+TBOkqjeluNA==", + "@chakra-ui/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/focus-lock": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-1.1.9.tgz", - "integrity": "sha512-C6nQqn5PNOiwp6Ovd9xzJ2V6P3d3ZdfykTl+Fc4YdTC47LTrJzJmv61++nhDAzYeEseojmmgXIE1DlZfGjZpZQ==", + "@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==" + }, + "@chakra-ui/skeleton": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", "requires": { - "@chakra-ui/utils": "1.8.1", - "react-focus-lock": "2.5.0" + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/form-control": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-1.3.8.tgz", - "integrity": "sha512-S4zHu9ktuUeiqFC/ZM95UQ8CrnJvuXKfFRG+HsQrO5JjvaiYl0YjDE79Bi6+oj5WHjz0Zo7t+px+LAjxn7my3Q==", + "@chakra-ui/skip-nav": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skip-nav/-/skip-nav-2.1.0.tgz", + "integrity": "sha512-Hk+FG+vadBSH0/7hwp9LJnLjkO0RPGnx7gBJWI4/SpoJf3e4tZlWYtwGj0toYY4aGKl93jVghuwGbDBEMoHDug==", + "requires": {} + }, + "@chakra-ui/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-lUOBcLMCnFZiA/s2NONXhELJh6sY5WtbRykPtclGfynqqOo47lwWJx+VP7xaeuhDOPcWSSecWc9Y1BfPOCz9cQ==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-pan-event": "2.1.0", + "@chakra-ui/react-use-size": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" } }, - "@chakra-ui/hooks": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-1.5.4.tgz", - "integrity": "sha512-xAFj2Feu+ZWD1oxbQQ2UHDI7zbx/zZXjlS6ogdpXZoMrGYJhbdbV0JNGx4eK1Q1AEChNLdnZQIq8An1gYKgE8g==", + "@chakra-ui/spinner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.1.0.tgz", + "integrity": "sha512-hczbnoXt+MMv/d3gE+hjQhmkzLiKuoTo42YhUG7Bs9OSv2lg1fZHW1fGNRFP3wTi6OIbD044U1P9HK+AOgFH3g==", "requires": { - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "compute-scroll-into-view": "1.0.14", - "copy-to-clipboard": "3.3.1" + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/icon": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-1.1.10.tgz", - "integrity": "sha512-AZ2dKCHKT6dI4K9NXizHsNZSwPuBP0i1BZ4ZPoXGMOfNt7bD3yKBLoZfyO+NmAubMHanVASztikSNAmy2Rvczg==", + "@chakra-ui/stat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", "requires": { - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/icons": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-1.0.14.tgz", - "integrity": "sha512-VM21FkQc4rWcES1D6ddNIq6VYaCnTwWBIaqM9GRQZ7FpsLeVNk6UFYiE8MMtGWVIXq3k9jEYLbQHm7YdEF9yLQ==", + "@chakra-ui/stepper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.3.1.tgz", + "integrity": "sha512-ky77lZbW60zYkSXhYz7kbItUpAQfEdycT0Q4bkHLxfqbuiGMf8OmgZOQkOB9uM4v0zPwy2HXhe0vq4Dd0xa55Q==", "requires": { - "@chakra-ui/icon": "1.1.10", - "@types/react": "^17.0.0" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/image": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-1.0.17.tgz", - "integrity": "sha512-M6OGT2Qs9Gy8Ba21XTWFDKe97fALSOSAcpQ38seSQt2hBjYdf8Pa3nKN6OO4O5zpTe612A/Sawuwxhf+6fSCeQ==", + "@chakra-ui/styled-system": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", + "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" } }, - "@chakra-ui/input": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-1.2.8.tgz", - "integrity": "sha512-WGvkcjJH9XpOlpKI9POn7UDA8qnHf22mBKY771U3IfW2QxcZH/rPFwDE7YIMLr9M4g+rL4NLSWmXYvO92rzc6A==", + "@chakra-ui/switch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.1.2.tgz", + "integrity": "sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==", "requires": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/layout": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-1.4.7.tgz", - "integrity": "sha512-wu1IBz/zg8rj4N88w4MtjS2kC5w+FXEvbxt0r2DqxLtPUFtE/fFmCa8OKsz+jMrDcZ1dRh48YNYrrWdAGEOQ8w==", + "@chakra-ui/system": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", + "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", "requires": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/color-mode": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.2" + }, + "dependencies": { + "@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "requires": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "requires": { + "@types/lodash": "*" + } + }, + "framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "requires": { + "tslib": "2.4.0" + } + }, + "react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } } }, - "@chakra-ui/live-region": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-1.0.13.tgz", - "integrity": "sha512-bzgi8jIYxVaqSVmUynnGFDjBOKf1LuKY1qMljuwIa7rK6iJZiMxTAdPbxX5Km4xTdgUz5AtZrmqDvKKLPDA1fg==", + "@chakra-ui/table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.1.0.tgz", + "integrity": "sha512-o5OrjoHCh5uCLdiUb0Oc0vq9rIAeHSIRScc2ExTC9Qg/uVZl2ygLrjToCaKfaaKl1oQexIeAcZDKvPG8tVkHyQ==", "requires": { - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/media-query": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-1.1.1.tgz", - "integrity": "sha512-KHsY4NzMl77yMyqpw3nleh1xM3zqAhCmSRBzQIh5fU/kT7r2tCwGl53djY5O2pl9VPMb4LhqPwkNd6vsscfCxQ==", + "@chakra-ui/tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-3.0.0.tgz", + "integrity": "sha512-6Mlclp8L9lqXmsGWF5q5gmemZXOiOYuh0SGT/7PgJVNPz3LXREXlXg2an4MBUD8W5oTkduCX+3KTMCwRrVrDYw==", "requires": { - "@chakra-ui/react-env": "1.0.5", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/menu": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-1.7.1.tgz", - "integrity": "sha512-a9+iyw+cUBtxC/+mKAhPS92a0Nlq94wXpz8haswWTNSOLE5U/zXNDbiG8BsXQ+pS8ngPUjZRE35EFSge+efV8Q==", + "@chakra-ui/tag": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.1.1.tgz", + "integrity": "sha512-Bdel79Dv86Hnge2PKOU+t8H28nm/7Y3cKd4Kfk9k3lOpUh4+nkSGe58dhRzht59lEqa4N9waCgQiBdkydjvBXQ==", "requires": { - "@chakra-ui/clickable": "1.1.6", - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" } }, - "@chakra-ui/modal": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-1.8.9.tgz", - "integrity": "sha512-fguU4zpE/4JWKY0yHyi/PoM0QzcBokgcT3KZnZj3KGOc1C15ZkR6GvD5UBubGMWQzlKT9hCwYaLc+VeoHnN6XA==", + "@chakra-ui/textarea": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.1.2.tgz", + "integrity": "sha512-ip7tvklVCZUb2fOHDb23qPy/Fr2mzDOGdkrpbNi50hDCiV4hFX02jdQJdi3ydHZUyVgZVBKPOJ+lT9i7sKA2wA==", "requires": { - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/focus-lock": "1.1.9", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/portal": "1.2.7", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.4.1" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" } }, - "@chakra-ui/number-input": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-1.2.8.tgz", - "integrity": "sha512-f8mQrPJu7O5qX4auNu24N6TtzaAE/q+eld1K+vwVdFUeFCOxuSsEoMT3xOEPrkEKYtikFDt0Dy3+pYrTcgBrvA==", + "@chakra-ui/theme": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", + "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", "requires": { - "@chakra-ui/counter": "1.1.7", - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" } }, - "@chakra-ui/pin-input": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-1.6.3.tgz", - "integrity": "sha512-BZYNUpcwagjfAr8olmkZe5aQ3e45q4rwoIwWvHVb39KVvPP3L7jzLFlxzoncoxVfBh9hOEztg/GeIeN0arLtLw==", + "@chakra-ui/theme-tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", + "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", "requires": { - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" } }, - "@chakra-ui/popover": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-1.8.1.tgz", - "integrity": "sha512-fEYcEV6rO4H9ewj+8nom5flHZfh8+BwxNfuzVZFnJbzuSzP9NKk5VMp+nbBow2CKlI/ct3Y8dpaLbsYrm/X6AA==", + "@chakra-ui/theme-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", + "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", "requires": { - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/popper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-2.2.1.tgz", - "integrity": "sha512-W0hMTBp2X62UooF3qPNmsEW0IJfz72gr2DN8nsCvHQrMiARB9s2jECEss6qEsB97tnmIG8k2TNee8IzTGLmMyA==", - "requires": { - "@chakra-ui/react-utils": "1.1.2", - "@popperjs/core": "2.4.4" - } - }, - "@chakra-ui/portal": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-1.2.7.tgz", - "integrity": "sha512-s5iFEhjZ1r5cyIH3i5R6UOW5FwmM3JDFkLw3Y7wumlYV4CscV2/UwoKIbscR93COMGP+HPvfVDUZOB1woftQRA==", - "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/progress": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-1.1.11.tgz", - "integrity": "sha512-8cPvHI/TxQSP1DPs7nC1qnLPFFd2lzMs7GDk0AcORW+Be8BS0cJC5NV9wZJM4N8RUP4sK4nhkMfyq4GbrNzoLg==", - "requires": { - "@chakra-ui/theme-tools": "1.1.8", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/radio": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-1.3.8.tgz", - "integrity": "sha512-3HWS7OVrdtqZYR/FBtIQhVvVLU0hiWZWWdiG+W1g6V3YhTq1PtwDA8uYDDe5KxaA/DjXfUhg1mQjjozgB1jZ/g==", - "requires": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" - } - }, - "@chakra-ui/react": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-1.6.5.tgz", - "integrity": "sha512-kvBNX3gkg2CCbdaj585I8m7Wd+PGMLTpEM15WbII3t6E26lhKWwD5OXMomhWhsnBMCM9uSQ790dunhffcruUUg==", - "requires": { - "@chakra-ui/accordion": "1.3.4", - "@chakra-ui/alert": "1.2.6", - "@chakra-ui/avatar": "1.2.7", - "@chakra-ui/breadcrumb": "1.2.7", - "@chakra-ui/button": "1.4.1", - "@chakra-ui/checkbox": "1.5.4", - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/control-box": "1.0.14", - "@chakra-ui/counter": "1.1.7", - "@chakra-ui/css-reset": "1.0.0", - "@chakra-ui/editable": "1.2.7", - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/image": "1.0.17", - "@chakra-ui/input": "1.2.8", - "@chakra-ui/layout": "1.4.7", - "@chakra-ui/live-region": "1.0.13", - "@chakra-ui/media-query": "1.1.1", - "@chakra-ui/menu": "1.7.1", - "@chakra-ui/modal": "1.8.9", - "@chakra-ui/number-input": "1.2.8", - "@chakra-ui/pin-input": "1.6.3", - "@chakra-ui/popover": "1.8.1", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/portal": "1.2.7", - "@chakra-ui/progress": "1.1.11", - "@chakra-ui/radio": "1.3.8", - "@chakra-ui/react-env": "1.0.5", - "@chakra-ui/select": "1.1.12", - "@chakra-ui/skeleton": "1.1.16", - "@chakra-ui/slider": "1.2.7", - "@chakra-ui/spinner": "1.1.11", - "@chakra-ui/stat": "1.1.11", - "@chakra-ui/switch": "1.2.7", - "@chakra-ui/system": "1.7.1", - "@chakra-ui/table": "1.2.5", - "@chakra-ui/tabs": "1.5.3", - "@chakra-ui/tag": "1.1.11", - "@chakra-ui/textarea": "1.1.12", - "@chakra-ui/theme": "1.9.2", - "@chakra-ui/toast": "1.2.9", - "@chakra-ui/tooltip": "1.3.8", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" - } - }, - "@chakra-ui/react-env": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-1.0.5.tgz", - "integrity": "sha512-qAWslmm27q7DyHv5XvIoW6ihmilQK6K/LNc0bUlPrKaxzLtk9m16N767spl+xue9JyPb7ZE3gAPwdUEUD7XKhQ==", - "requires": { - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/react-utils": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-1.1.2.tgz", - "integrity": "sha512-S8jPVKGZH2qF7ZGxl/0DF/dXXI2AxDNGf4Ahi2LGHqajMvqBB7vtYIRRmIA7+jAnErhzO8WUi3i4Z7oScp6xSA==", - "requires": { - "@chakra-ui/utils": "^1.7.0" - } - }, - "@chakra-ui/select": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-1.1.12.tgz", - "integrity": "sha512-oOCLLCONoGgnJ/RvWEvdl+ggecDGIlxYHOsTjPu2vZs6PPIer69Xf9/S36Zp4kkuYWxz2ssK3YMoiU0PpPz7GQ==", - "requires": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/skeleton": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-1.1.16.tgz", - "integrity": "sha512-pzqa2PYg21ktFrdIcMvx+BEG4u+tTNuHDHqQeFD7bV7tYbNkMlQhY7I7kTBWMo0mROmnrerVBTJd92CbG/c5lA==", - "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/media-query": "1.1.1", - "@chakra-ui/system": "1.7.1", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/slider": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-1.2.7.tgz", - "integrity": "sha512-fp5ef8MEbXq89U4TpSeEa6NUwvtSyHbM6VSdZCgsHG546BWpRkcCEvagtKXmviX4NthtOyig0YCqmET8HKduVA==", - "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/spinner": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-1.1.11.tgz", - "integrity": "sha512-gkh44jZ8msfHQgswVvflbWz/6Egv5FeSu6a7BJWX/XQJw9IxPy0B75xy0d06LgQCOFk17x2xhB+mwZI6i55T8Q==", - "requires": { - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" - } - }, - "@chakra-ui/stat": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-1.1.11.tgz", - "integrity": "sha512-47aHxoAReUmQ0bU6q7qY2N9RryKtZWTheK/xepFppGI5Q0hWSoOESkJ8BNZ/LuQW6NLCmv2jOxyhW4XIDEJ+fA==", - "requires": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" - } - }, - "@chakra-ui/styled-system": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-1.12.1.tgz", - "integrity": "sha512-/92egMOe6/6xerCmoos1/HhZBJdeRwIRa2BR+wwkHJ4ehqxi4IBtU9oXc2g4P70GGh6UqKIgR/oURrvVY8vjow==", - "requires": { - "@chakra-ui/utils": "1.8.1", - "csstype": "^3.0.6" - } - }, - "@chakra-ui/switch": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-1.2.7.tgz", - "integrity": "sha512-zHI6lg+NuDUw9vxEDSOkH4j2lRntIpwysuIEYUKFPkH2zmZpo6c1zLA9L+rfMbqFRoewm+YIqh8tOgQmNbIGPg==", - "requires": { - "@chakra-ui/checkbox": "1.5.4", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/system": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-1.7.1.tgz", - "integrity": "sha512-1G7+mAPbkGqtowZ4Bt9JwCB2wTJt701vj/vPLRW2KDYqlES5Xp2RomG8LdrGQcVWfiwO2wzpCYUZj2YLY4kbVA==", - "requires": { - "@chakra-ui/color-mode": "1.1.10", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/styled-system": "1.12.1", - "@chakra-ui/utils": "1.8.1", - "react-fast-compare": "3.2.0" - }, - "dependencies": { - "react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - } - } - }, - "@chakra-ui/table": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-1.2.5.tgz", - "integrity": "sha512-iYSDv4oTKZ8bLJo9OHjAPCi7cxDXXVXIYupwP2oXcBsM8Hx6FrmlPlO8vdBCTD2ySaazFOZgW2/EPOKsXlAnlQ==", - "requires": { - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/tabs": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-1.5.3.tgz", - "integrity": "sha512-Nn/+gSZRigODwPK597U6DYwaPiOZAFNsozE5RYSZootr/tMIwqTh3opxwzW9zbPx4lQ2+3uvS4QHN5Tn+YxW8Q==", - "requires": { - "@chakra-ui/clickable": "1.1.6", - "@chakra-ui/descendant": "2.0.1", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/tag": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-1.1.11.tgz", - "integrity": "sha512-XLKafTuK5lsRLk+zAXCQZ1368GOTf59ghtpYofLg0ieGAbOOuNmw1/lLKdnrnHj8ueatKPr86bDa4DQ31J3Lxg==", - "requires": { - "@chakra-ui/icon": "1.1.10", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/textarea": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-1.1.12.tgz", - "integrity": "sha512-Qmc98ePiSdjCJ/AVCQ6mgX7Ez/cEoBTPkP/t4eqbjpfBSWYAExfYn/w/Tkcx1C5dd9cfk+EPzxM2r3KVpWuQGA==", - "requires": { - "@chakra-ui/form-control": "1.3.8", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/theme": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-1.9.2.tgz", - "integrity": "sha512-bSKcVGTi83sjdQNJULLAul0mL3Hljs+KEZ+oWEl0FogPumCeBOBW4rPCnddW3YWkQUrHwoNz4hag29klTs/IsQ==", - "requires": { - "@chakra-ui/theme-tools": "1.1.8", - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/theme-tools": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-1.1.8.tgz", - "integrity": "sha512-FQqHNfuvl2O1m7o6YY3ozqxnz74TWAhVzzfKrh7/eXcyA2IkF+MuKMUnyWXjOq1bcLt9rAGq0FQALisTd4YPWQ==", - "requires": { - "@chakra-ui/utils": "1.8.1", - "@types/tinycolor2": "1.4.2", - "tinycolor2": "1.4.2" + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1", + "lodash.mergewith": "4.6.2" } }, "@chakra-ui/toast": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-1.2.9.tgz", - "integrity": "sha512-fVE5UD27WykiPS817Wlee4LAT01SysWFxCFikflBj1nK8UJXhRKV/UavNf5aJbxvzx5QCwkD0pjFmDO9uxOSPA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-7.0.2.tgz", + "integrity": "sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==", "requires": { - "@chakra-ui/alert": "1.2.6", - "@chakra-ui/close-button": "1.1.10", - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/theme": "1.9.2", - "@chakra-ui/transition": "1.3.3", - "@chakra-ui/utils": "1.8.1", - "@reach/alert": "0.13.2" + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-timeout": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1" } }, "@chakra-ui/tooltip": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-1.3.8.tgz", - "integrity": "sha512-7rqAhcd04ZnnJZ2DmGvVPNyi/+Fy4bzQocYn83rWR3LC/8/LM+czG6pmz4FKjYR5iU6Ttf6Ckp8NfFKhyHAp/g==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.3.1.tgz", + "integrity": "sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==", "requires": { - "@chakra-ui/hooks": "1.5.4", - "@chakra-ui/popper": "2.2.1", - "@chakra-ui/portal": "1.2.7", - "@chakra-ui/react-utils": "1.1.2", - "@chakra-ui/utils": "1.8.1", - "@chakra-ui/visually-hidden": "1.0.13" + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/transition": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-1.3.3.tgz", - "integrity": "sha512-p9ZRaHNdSGQKS3trL7jSxh47fQDDEZfgYHMx7L/mDy6vxMNsO6YhnURULePk90hvtCAp6Z4urNTM6VYaywioQQ==", - "requires": { - "@chakra-ui/utils": "1.8.1" - } - }, - "@chakra-ui/utils": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-1.8.1.tgz", - "integrity": "sha512-v0xL9U2ozDbHCl2kQTdJNOjUGT7ZjyFwEYuMW02ZaLkmLPj2w3G592iOsJ9Z9sBemQgoOrZGyTWqdxm6rhxJug==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.1.0.tgz", + "integrity": "sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==", "requires": { - "@types/lodash.mergewith": "4.6.6", - "css-box-model": "1.2.1", - "framesync": "5.3.0", - "lodash.mergewith": "4.6.2" + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/visually-hidden": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-1.0.13.tgz", - "integrity": "sha512-wFFXdejxwOT7r7AbD/IFl6Ve+n6VIOl2Drjcrn3JXmfwzL9NKB3xrtcdMXe8G/zW9jRXh+E6DUkTyEUjdUZErg==", - "requires": { - "@chakra-ui/utils": "1.8.1" - } + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.2.0.tgz", + "integrity": "sha512-KmKDg01SrQ7VbTD3+cPWf/UfpF5MSwm3v7MWi0n5t8HnnadT13MF0MJCDSXbBWnzLv1ZKJ6zlyAOeARWX+DpjQ==", + "requires": {} }, "@cnakazawa/watch": { "version": "1.0.4", @@ -22974,50 +25048,27 @@ "dev": true }, "@emotion/babel-plugin": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.3.0.tgz", - "integrity": "sha512-UZKwBV2rADuhRp+ZOGgNWg2eYgbzKzQXfQPtJbu/PLy8onurxlNCLvxMQEvlr1/GudguPI5IU9qIY1+2z1M5bA==", - "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/runtime": "^7.13.10", - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.5", - "@emotion/serialize": "^1.0.2", - "babel-plugin-macros": "^2.6.1", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "^4.0.3" + "stylis": "4.2.0" }, "dependencies": { "@emotion/memoize": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", - "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" - }, - "babel-plugin-macros": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", - "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", - "requires": { - "@babel/runtime": "^7.7.2", - "cosmiconfig": "^6.0.0", - "resolve": "^1.12.0" - } - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "escape-string-regexp": { "version": "4.0.0", @@ -23027,21 +25078,28 @@ } }, "@emotion/cache": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.4.0.tgz", - "integrity": "sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", "requires": { - "@emotion/memoize": "^0.7.4", - "@emotion/sheet": "^1.0.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "stylis": "^4.0.3" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + } } }, "@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, "@emotion/is-prop-valid": { "version": "0.8.8", @@ -23055,130 +25113,314 @@ "@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true }, "@emotion/react": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.4.0.tgz", - "integrity": "sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg==", - "requires": { - "@babel/runtime": "^7.13.10", - "@emotion/cache": "^11.4.0", - "@emotion/serialize": "^1.0.2", - "@emotion/sheet": "^1.0.1", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.0.tgz", + "integrity": "sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.0", + "@emotion/cache": "^11.10.0", + "@emotion/serialize": "^1.1.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", "hoist-non-react-statics": "^3.3.1" } }, "@emotion/serialize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", - "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", "requires": { - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.4", - "@emotion/unitless": "^0.7.5", - "@emotion/utils": "^1.0.0", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", "csstype": "^3.0.2" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + } } }, "@emotion/sheet": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.1.tgz", - "integrity": "sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "@emotion/styled": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.3.0.tgz", - "integrity": "sha512-fUoLcN3BfMiLlRhJ8CuPUMEyKkLEoM+n+UyAbnqGEsCd5IzKQ7VQFLtzpJOaCD2/VR2+1hXQTnSZXVJeiTNltA==", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.0.tgz", + "integrity": "sha512-V9oaEH6V4KePeQpgUE83i8ht+4Ri3E8Djp/ZPJ4DQlqWhSKITvgzlR3/YQE2hdfP4Jw3qVRkANJz01LLqK9/TA==", "requires": { - "@babel/runtime": "^7.13.10", - "@emotion/babel-plugin": "^11.3.0", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/serialize": "^1.0.2", - "@emotion/utils": "^1.0.0" + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.0", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.0", + "@emotion/utils": "^1.2.0" }, "dependencies": { "@emotion/is-prop-valid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.0.tgz", - "integrity": "sha512-9RkilvXAufQHsSsjQ3PIzSns+pxuX4EW8EbGeSPjZMHuMx6z/MOzb9LpqNieQX4F3mre3NWS2+X3JNRHTQztUQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", "requires": { - "@emotion/memoize": "^0.7.4" + "@emotion/memoize": "^0.8.0" } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" } } }, "@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" }, "@emotion/utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz", - "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" }, "@emotion/weak-memoize": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, - "@endemolshinegroup/cosmiconfig-typescript-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", - "integrity": "sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==", + "@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "requires": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" + "eslint-visitor-keys": "^3.3.0" }, "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true } } }, "@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", - "globals": "^12.1.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "dependencies": { "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "ignore": { @@ -23194,97 +25436,242 @@ "dev": true }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, - "@graphql-tools/mock": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.1.3.tgz", - "integrity": "sha512-xtY3amuEdPLeoSALNN4cEaOmietbVaxFAVfkn08v0AHr7zfXyy+sCLn98y8BXxTaow8/nTMBCTdCZ5Qe9gtbQQ==", + "@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" + }, + "@graphql-tools/merge": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.3.3.tgz", + "integrity": "sha512-EfULshN2s2s2mhBwbV9WpGnoehRLe7eIMdZrKfHhxlBWOvtNUd3KSCN0PUdAMd7lj1jXUW9KYdn624JrVn6qzg==", "dev": true, "requires": { - "@graphql-tools/schema": "^7.0.0", - "@graphql-tools/utils": "^7.0.0", - "fast-json-stable-stringify": "^2.1.0", - "ts-is-defined": "^1.0.0", - "tslib": "~2.2.0" + "@graphql-tools/utils": "8.10.0", + "tslib": "^2.4.0" }, "dependencies": { + "@graphql-tools/utils": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-yI+V373FdXQbYfqdarehn9vRWDZZYuvyQ/xwiv5ez2BbobHrqsexF7qs56plLRaQ8ESYpVAjMQvJWe9s23O0Jg==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true } } }, - "@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", + "@graphql-tools/mock": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.7.3.tgz", + "integrity": "sha512-U9e3tZenFvSTf0TAaFgwqO84cGNEbgzWXvboqJPth873dMag8sOlLyOBZceVzAZP7ptwfLbhm3S0Qq4ffI7mCw==", "dev": true, "requires": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" + "@graphql-tools/schema": "9.0.1", + "@graphql-tools/utils": "8.10.0", + "fast-json-stable-stringify": "^2.1.0", + "tslib": "^2.4.0" }, "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } + "@graphql-tools/schema": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.1.tgz", + "integrity": "sha512-Y6apeiBmvXEz082IAuS/ainnEEQrzMECP1MRIV72eo2WPa6ZtLYPycvIbd56Z5uU2LKP4XcWRgK6WUbCyN16Rw==", + "dev": true, + "requires": { + "@graphql-tools/merge": "8.3.3", + "@graphql-tools/utils": "8.10.0", + "tslib": "^2.4.0", + "value-or-promise": "1.0.11" + } + }, + "@graphql-tools/utils": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-yI+V373FdXQbYfqdarehn9vRWDZZYuvyQ/xwiv5ez2BbobHrqsexF7qs56plLRaQ8ESYpVAjMQvJWe9s23O0Jg==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "value-or-promise": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", + "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==", + "dev": true + } + } + }, + "@graphql-tools/schema": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.0.tgz", + "integrity": "sha512-kf3qOXMFcMs2f/S8Y3A8fm/2w+GaHAkfr3Gnhh2LOug/JgpY/ywgFVxO3jOeSpSEdoYcDKLcXVjMigNbY4AdQg==", + "dev": true, + "requires": { + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "dependencies": { + "@graphql-tools/merge": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.0.tgz", + "integrity": "sha512-J7/xqjkGTTwOJmaJQJ2C+VDBDOWJL3lKrHJN4yMaRLAJH3PosB7GiPRaSDZdErs0+F77sH2MKs2haMMkywzx7Q==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0" + } + }, + "tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + } + } }, "@graphql-tools/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.4.tgz", + "integrity": "sha512-MF+nZgGROSnFgyOYWhrl2PuJMlIBvaCH48vtnlnDQKSeDc2fUfOzUVloBAQvnYmK9JBmHHks4Pxv25Ybg3r45Q==", "dev": true, "requires": { - "@ardatan/aggregate-error": "0.0.6", - "camel-case": "4.1.2", - "tslib": "~2.2.0" + "@graphql-typed-document-node/core": "^3.1.1", + "dset": "^3.1.2", + "tslib": "^2.4.0" }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", "dev": true } } }, "@graphql-typed-document-node/core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz", - "integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==", + "requires": {} }, - "@hot-loader/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-EN9czvcLsMYmSDo5yRKZOAq3ZGRlDpad1gPtX0NdMMomJXcPE3yFSeFzE94X/NjOaiSVimB7LuqPYpkWVaIi4Q==", + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "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" }, "dependencies": { - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" } } } @@ -23655,6 +26042,15 @@ } } }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "devOptional": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jest/source-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", @@ -23792,15 +26188,6 @@ "chalk": "^4.0.0" }, "dependencies": { - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -23858,135 +26245,243 @@ "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==", "dev": true }, - "@lingui/babel-plugin-extract-messages": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.10.2.tgz", - "integrity": "sha512-RBAw8mE/l6VtpunmgRllX2kotjvWhlo5skyySipQDmYRU7QWQV7z3F67UtaIyG4yHMqcdF3T/q9x45qcgtdDMw==", + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "devOptional": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "requires": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.10.2", - "mkdirp": "^1.0.4" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@juggle/resize-observer": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", + "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@lingui/babel-plugin-extract-messages": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-5.9.3.tgz", + "integrity": "sha512-zm6QHDILmhj8olgLL2zHQn18yFA5mf4hX7QzCr1OOI/e815I0IkecCYue1Ych+y+B+V0eLriiW8AcfpDRCQFFw==", + "dev": true + }, + "@lingui/babel-plugin-lingui-macro": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-5.9.3.tgz", + "integrity": "sha512-fLMhBarRsuqBGOq2YuEoqOjEAV2VNezz/+f/Dn0vLFHF/kAjnFwTHb8pL8DRSIMsWG16mPrGnLpdROZBmJlFtA==", + "devOptional": true, + "requires": { + "@babel/core": "^7.20.12", + "@babel/runtime": "^7.20.13", + "@babel/types": "^7.20.7", + "@lingui/conf": "5.9.3", + "@lingui/core": "5.9.3", + "@lingui/message-utils": "5.9.3" } }, "@lingui/cli": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.10.2.tgz", - "integrity": "sha512-kuKMSeWyRYh3aw1zZZkqOzt2wjy1TdObji+PDg1OyoOgWkvap1SlpcFQ76Fgn2u1e6MOyDpfwg+I7fGEmOA+gQ==", - "dev": true, - "requires": { - "@babel/generator": "^7.11.6", - "@babel/parser": "^7.11.5", - "@babel/plugin-syntax-jsx": "^7.10.4", - "@babel/runtime": "^7.11.2", - "@babel/types": "^7.11.5", - "@lingui/babel-plugin-extract-messages": "^3.10.2", - "@lingui/conf": "^3.10.2", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-5.9.3.tgz", + "integrity": "sha512-KEE0J4eGlfpiLZ+l019qjraWfqfh5mUmQSJeTFw5PulO4v50zvxw5tDX8stpBzJ3QtgUQZlrMUh0OTGdURaAMg==", + "dev": true, + "requires": { + "@babel/core": "^7.21.0", + "@babel/generator": "^7.21.1", + "@babel/parser": "^7.22.0", + "@babel/runtime": "^7.21.0", + "@babel/types": "^7.21.2", + "@lingui/babel-plugin-extract-messages": "5.9.3", + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "@lingui/conf": "5.9.3", + "@lingui/core": "5.9.3", + "@lingui/format-po": "5.9.3", + "@lingui/message-utils": "5.9.3", "chokidar": "3.5.1", - "cli-table": "^0.3.1", - "commander": "^6.1.0", - "date-fns": "^2.16.1", - "fs-extra": "^9.0.1", - "fuzzaldrin": "^2.1.0", - "glob": "^7.1.4", - "inquirer": "^7.3.3", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3", - "micromatch": "4.0.2", - "mkdirp": "^1.0.4", - "node-gettext": "^3.0.0", + "cli-table": "^0.3.11", + "commander": "^10.0.0", + "convert-source-map": "^2.0.0", + "date-fns": "^3.6.0", + "esbuild": "^0.25.1", + "glob": "^11.0.0", + "micromatch": "^4.0.7", + "ms": "^2.1.3", "normalize-path": "^3.0.0", "ora": "^5.1.0", - "papaparse": "^5.3.0", - "pkg-up": "^3.1.0", - "plurals-cldr": "^1.0.4", - "pofile": "^1.1.0", - "pseudolocale": "^1.1.0", - "ramda": "^0.27.1" + "picocolors": "^1.1.1", + "pofile": "^1.1.4", + "pseudolocale": "^2.0.0", + "source-map": "^0.7.6", + "threads": "^1.7.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "balanced-match": "^4.0.2" } }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "brace-expansion": "^5.0.5" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, "@lingui/conf": { - "version": "3.10.4", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.10.4.tgz", - "integrity": "sha512-ywAYoALnGa8GLuRaC6jW2FKkkPF6TQBUs2ACth8lKHjvJNz/Q3ZYKkK+hQj4iLODc5jaCophACrpNrZj1ef/tw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "jest-validate": "^26.5.2", - "lodash.get": "^4.4.2" - }, - "dependencies": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-5.9.3.tgz", + "integrity": "sha512-hVEoYHmO2A3XmFX4A5RuBgcoVBoM7Xgoqejeq25XELvesJj2s2T15F47TA5n3/S7iTqngd6n/8KxBli9TYwgqQ==", + "devOptional": true, + "requires": { + "@babel/runtime": "^7.20.13", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^2.5.1", + "picocolors": "^1.1.1" + }, + "dependencies": { + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "devOptional": true, + "requires": { + "@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" + } + }, + "@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "devOptional": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "requires": { "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true + }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -23996,7 +26491,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "requires": { "color-name": "~1.1.4" } @@ -24005,19 +26500,85 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devOptional": true + }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "devOptional": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "devOptional": true + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "devOptional": true + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "devOptional": true, + "requires": { + "@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" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "devOptional": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "devOptional": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "devOptional": true + } + } + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "devOptional": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "requires": { "has-flag": "^4.0.0" } @@ -24025,262 +26586,130 @@ } }, "@lingui/core": { - "version": "3.10.4", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz", - "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-5.9.3.tgz", + "integrity": "sha512-3b8LnDjx8POdQ6q6UKBe2DHynyQFCO66vm8/UPQnvlQowUk4Xdu5bK6oet11D9/vrSznrDDS+Qb5JVcNBUImgg==", + "requires": { + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.9.3" + } + }, + "@lingui/format-po": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-5.9.3.tgz", + "integrity": "sha512-+LMnhWl7EmXrdOv10gopE1g8w8vtPY5Fxk72OORrGQFVMGBIioz4BEnKrNdV1ek2M+GxoMZtnUs17KrJN5Jv9A==", + "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" + "@lingui/conf": "5.9.3", + "@lingui/message-utils": "5.9.3", + "date-fns": "^3.6.0", + "pofile": "^1.1.4" } }, "@lingui/loader": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.10.2.tgz", - "integrity": "sha512-6iktLXTnZktm5V1ZaJqh7LQxQuPf2f/SkxzLrIS8eIRkMYX5Pf+XbUQH1b9aZisZp2gToHLKDpHx+w5PbfH0Ug==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-5.9.3.tgz", + "integrity": "sha512-V+m8vfZ1doPSc26fPZa1zVso75nl70mgdz51OHGAvFfafyDqYasFHmBf5xihS58vy6LXuep3K19Ymf6TG6i5TQ==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.10.2", - "@lingui/conf": "^3.10.2", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" + "@babel/runtime": "^7.20.13", + "@lingui/cli": "5.9.3", + "@lingui/conf": "5.9.3" } }, "@lingui/macro": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.10.2.tgz", - "integrity": "sha512-nEYUj1F4TUK4aHVpXcAagg7zx41Z7e3wjI1OFlmXkKSupDggYCn6jgzyyx46pUEB8Y5pIzCESPzQIa1P2sTPJA==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-5.9.3.tgz", + "integrity": "sha512-xWqJ+hpp5T+xmE++VYlcfMxrF48LpY5Ak9N/luibY9d0AqvyYZiiv7Xaq7E2eK69v9CJWx+6eXA6uPhC8gHY+Q==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.10.2", - "ramda": "^0.27.1" + "@lingui/core": "5.9.3", + "@lingui/react": "5.9.3" + } + }, + "@lingui/message-utils": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-5.9.3.tgz", + "integrity": "sha512-oAK7HA7lcQrzaEaM6G1T5RwwxJxaSKfG/IFIxpZIl49TSFQv+s9YPNgHnVi+d4DmterpXNxy9ZZ+NtckJx6u7g==", + "requires": { + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" } }, "@lingui/react": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@lingui/react/-/react-3.10.2.tgz", - "integrity": "sha512-tjwSw/7JfbIjZ6G3viE1hM8YC85Yx+NMdEAlSzpwVfxJR66a437//rFVy4U7CmULW2ikNgUBZ47YMYaC6hkMrw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-5.9.3.tgz", + "integrity": "sha512-aje78l3zGGZ3C75fiGhDVKyVALHfiKlYFjcOlZpgXY/JAVfFuZX+6wUGG9x1A8k7BfxrDy/ofHIBahPvNAqoKw==", + "requires": { + "@babel/runtime": "^7.20.13", + "@lingui/core": "5.9.3" + } + }, + "@messageformat/parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz", + "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==", "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/core": "^3.10.2" + "moo": "^0.5.1" } }, "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", - "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "optional": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, + "@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "requires": { + "@noble/hashes": "^1.1.5" + } + }, "@popperjs/core": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz", - "integrity": "sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==" + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "dev": true }, "@protobufjs/base64": { @@ -24298,13 +26727,13 @@ "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", "dev": true }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.1", @@ -24314,89 +26743,43 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "dev": true }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", "dev": true }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "dev": true }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "dev": true }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "dev": true }, - "@reach/alert": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/alert/-/alert-0.13.2.tgz", - "integrity": "sha512-LDz83AXCrClyq/MWe+0vaZfHp1Ytqn+kgL5VxG7rirUvmluWaj/snxzfNPWn0Ma4K2YENmXXRC/iHt5X95SqIg==", - "requires": { - "@reach/utils": "0.13.2", - "@reach/visually-hidden": "0.13.2", - "prop-types": "^15.7.2", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } - }, - "@reach/utils": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.13.2.tgz", - "integrity": "sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==", - "requires": { - "@types/warning": "^3.0.0", - "tslib": "^2.1.0", - "warning": "^4.0.3" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } - }, - "@reach/visually-hidden": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.13.2.tgz", - "integrity": "sha512-sPZwNS0/duOuG0mYwE5DmgEAzW9VhgU3aIt1+mrfT/xiT9Cdncqke+kRBQgU708q/Ttm9tWsoHni03nn/SuPTQ==", - "requires": { - "prop-types": "^15.7.2", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } + "@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==" }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true + "@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "devOptional": true }, "@sinonjs/commons": { "version": "1.8.2", @@ -24416,29 +26799,20 @@ "@sinonjs/commons": "^1.7.0" } }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@testing-library/dom": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.26.5.tgz", - "integrity": "sha512-2v/fv0s4keQjJIcD4bjfJMFtvxz5icartxUWdIZVNJR539WD9oxVrvIAPw+3Ydg4RLgxt0rvQx3L9cAjCci0Kg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.13.0.tgz", + "integrity": "sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.10.3", + "@babel/runtime": "^7.12.5", "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.1", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", - "pretty-format": "^26.4.2" + "pretty-format": "^27.0.2" }, "dependencies": { "ansi-styles": { @@ -24481,6 +26855,31 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -24493,17 +26892,18 @@ } }, "@testing-library/jest-dom": { - "version": "5.11.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.5.tgz", - "integrity": "sha512-XI+ClHR864i6p2kRCEyhvpVejuer+ObVUF4cjCvRSF88eOMIfqw7RoS9+qoRhyigGswMfT64L6Nt0Ufotxbwtg==", + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", "dev": true, "requires": { + "@adobe/css-tools": "^4.0.1", "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^3.0.0", - "css": "^3.0.0", "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", "lodash": "^4.17.15", "redent": "^3.0.0" }, @@ -24560,38 +26960,44 @@ } }, "@testing-library/react": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.1.1.tgz", - "integrity": "sha512-DT/P2opE9o4NWCd/oIL73b6VF/Xk9AY8iYSstKfz9cXw0XYPQ5IhA/cuYfoN9nU+mAynW8DpAVfEWdM6e7zF6g==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", "dev": true, "requires": { - "@babel/runtime": "^7.12.1", - "@testing-library/dom": "^7.26.4" + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" } }, "@testing-library/react-hooks": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.0.tgz", - "integrity": "sha512-c/wvcz/Set+KOvbi07EQO7tujsUIp5HnNAygJoSpMTVkIDcp7JtSemhjRDg1WL6Qsw076inWobTKCepK3mgi8A==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz", + "integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@types/react": ">=16.9.0", "@types/react-dom": ">=16.9.0", "@types/react-test-renderer": ">=16.9.0", - "filter-console": "^0.1.1", "react-error-boundary": "^3.1.0" } }, "@testing-library/user-event": { - "version": "13.1.9", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.9.tgz", - "integrity": "sha512-NZr0zL2TMOs2qk+dNlqrAdbaRW5dAmYwd1yuQ4r7HpkVEOj0MWuUjDWwKhcLd/atdBy8ZSMHSKp+kXSQe47ezg==", + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5" } }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, "@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -24655,59 +27061,75 @@ } }, "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" } }, - "@types/connect": { - "version": "3.4.34", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", - "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", "dev": true, "requires": { "@types/node": "*" } }, - "@types/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg==", - "dev": true + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } }, - "@types/cookies": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.6.tgz", - "integrity": "sha512-FK4U5Qyn7/Sc5ih233OuHO0qAkOpEcD/eG6584yEiLKizTFRny86qHLe/rej3HFQrkBuUjF4whFliAdODbVN/w==", + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", "dev": true, "requires": { - "@types/connect": "*", - "@types/express": "*", - "@types/keygrip": "*", + "@types/express-serve-static-core": "*", "@types/node": "*" } }, "@types/cors": { - "version": "2.8.10", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", - "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, + "@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==" + }, "@types/d3-color": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", - "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.5.tgz", + "integrity": "sha512-5sNP3DmtSnSozxcjqmzQKsDOuVJXZkceo1KJScDc1982kk/TS9mTPc6lpli1gTu1MIBF1YWutpHpjucNWcIj5g==" }, - "@types/d3-interpolate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", - "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==" + }, + "@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==" + }, + "@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "requires": { - "@types/d3-color": "^1" + "@types/geojson": "*" } }, "@types/d3-path": { @@ -24716,17 +27138,9 @@ "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" }, "@types/d3-random": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz", - "integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA==" - }, - "@types/d3-scale": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", - "integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==", - "requires": { - "@types/d3-time": "^2" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.3.tgz", + "integrity": "sha512-Ghs4R3CcgJ3o6svszRzIH4b8PPYex/COo+rhhZjDAs+bVducXwjmVSi27WcDOaLLCBV2t3tfVH9bYXAL76IvQA==" }, "@types/d3-shape": { "version": "1.3.8", @@ -24737,14 +27151,19 @@ } }, "@types/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.4.tgz", + "integrity": "sha512-BTfLsxTeo7yFxI/haOOf1ZwJ6xKgQLT9dCp+EcmQv87Gox6X+oKl4mLKfO6fnWm3P22+A6DknMNEZany8ql2Rw==" + }, + "@types/d3-time-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz", + "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==" }, "@types/eslint": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.4.tgz", - "integrity": "sha512-YCY4kzHMsHoyKspQH+nwSe+70Kep7Vjt2X+dZe5Vs2vkRudqtoFoUIv1RlJmZB8Hbp7McneupoZij4PadxsK5Q==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "requires": { "@types/estree": "*", @@ -24752,9 +27171,9 @@ } }, "@types/eslint-scope": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", - "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "requires": { "@types/eslint": "*", @@ -24762,15 +27181,15 @@ } }, "@types/estree": { - "version": "0.0.45", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", - "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", "dev": true, "requires": { "@types/body-parser": "*", @@ -24780,9 +27199,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", "dev": true, "requires": { "@types/node": "*", @@ -24790,14 +27209,10 @@ "@types/range-parser": "*" } }, - "@types/fs-capacitor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz", - "integrity": "sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ==", - "dev": true, - "requires": { - "@types/node": "*" - } + "@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" }, "@types/glob": { "version": "7.1.3", @@ -24819,38 +27234,44 @@ } }, "@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==", - "dev": true - }, - "@types/http-assert": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", - "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, - "@types/http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==", - "dev": true + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true + "devOptional": true }, "@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, + "devOptional": true, "requires": { "@types/istanbul-lib-coverage": "*" } }, + "@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "devOptional": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, "@types/jest": { "version": "26.0.15", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz", @@ -24862,65 +27283,26 @@ } }, "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/keygrip": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", - "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", - "dev": true - }, - "@types/koa": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.1.tgz", - "integrity": "sha512-Qbno7FWom9nNqu0yHZ6A0+RWt4mrYBhw3wpBAQ3+IuzGcLlfeYkzZrnMq5wsxulN2np8M4KKeUpTodsOsSad5Q==", - "dev": true, - "requires": { - "@types/accepts": "*", - "@types/content-disposition": "*", - "@types/cookies": "*", - "@types/http-assert": "*", - "@types/http-errors": "*", - "@types/keygrip": "*", - "@types/koa-compose": "*", - "@types/node": "*" - } - }, - "@types/koa-compose": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz", - "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", - "dev": true, - "requires": { - "@types/koa": "*" - } - }, - "@types/lodash": { - "version": "4.14.171", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", - "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==" - }, - "@types/lodash.mergewith": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz", - "integrity": "sha512-RY/8IaVENjG19rxTZu9Nukqh0W2UrYgmBj5sdns4hWRZaV8PqR7wIKHFKzvOTjo4zVRV7sVI+yFhAJql12Kfqg==", - "requires": { - "@types/lodash": "*" - } - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "dev": true }, "@types/mime": { @@ -24939,7 +27321,16 @@ "version": "14.0.26", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz", "integrity": "sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA==", - "dev": true + "devOptional": true + }, + "@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/normalize-package-data": { "version": "2.4.0", @@ -24964,21 +27355,21 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, "@types/react": { - "version": "17.0.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz", - "integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==", + "version": "17.0.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", + "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -24986,9 +27377,9 @@ } }, "@types/react-dom": { - "version": "16.9.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", - "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "requires": { "@types/react": "*" } @@ -25002,21 +27393,51 @@ "@types/react": "*" } }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/serve-static": { - "version": "1.13.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", - "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", "dev": true, "requires": { "@types/mime": "^1", "@types/node": "*" } }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -25044,11 +27465,6 @@ "@types/jest": "*" } }, - "@types/tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==" - }, "@types/uglify-js": { "version": "3.9.3", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", @@ -25066,11 +27482,6 @@ } } }, - "@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" - }, "@types/webpack": { "version": "4.41.21", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", @@ -25113,9 +27524,9 @@ } }, "@types/ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "requires": { "@types/node": "*" @@ -25134,57 +27545,37 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true - }, - "@types/zen-observable": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz", - "integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg==" - }, - "@typescript-eslint/experimental-utils": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.5.0.tgz", - "integrity": "sha512-bW9IpSAKYvkqDGRZzayBXIgPsj2xmmVHLJ+flGSoN0fF98pGoKFhbunIol0VF2Crka7z984EEhFi623Rl7e6gg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.5.0", - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/typescript-estree": "4.5.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } + "devOptional": true }, "@typescript-eslint/scope-manager": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.5.0.tgz", - "integrity": "sha512-C0cEO0cTMPJ/w4RA/KVe4LFFkkSh9VHoFzKmyaaDWAnPYIEzVCtJ+Un8GZoJhcvq+mPFXEsXa01lcZDHDG6Www==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "requires": { - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/visitor-keys": "4.5.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" } }, "@typescript-eslint/types": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.5.0.tgz", - "integrity": "sha512-n2uQoXnyWNk0Les9MtF0gCK3JiWd987JQi97dMSxBOzVoLZXCNtxFckVqt1h8xuI1ix01t+iMY4h4rFMj/303g==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.5.0.tgz", - "integrity": "sha512-gN1mffq3zwRAjlYWzb5DanarOPdajQwx5MEWkWCk0XvqC8JpafDTeioDoow2L4CA/RkYZu7xEsGZRhqrTsAG8w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "requires": { - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/visitor-keys": "4.5.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "dependencies": { "array-union": { @@ -25194,25 +27585,25 @@ "dev": true }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, @@ -25223,50 +27614,139 @@ "dev": true }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, "@typescript-eslint/visitor-keys": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.5.0.tgz", - "integrity": "sha512-UHq4FSa55NDZqscRU//O5ROFhHa9Hqn9KWTEvJGTArtTQp5GKv9Zqf6d/Q3YXXcFv4woyBml7fJQlQ+OuqRcHA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.5.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true } } }, "@visx/axis": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.17.1.tgz", - "integrity": "sha512-3JdAY8xwA4xVnzkbXdIzCOWYCknCgw3L185lOJTXWNGO7kIgzbQ2YrLXnet37BFgD83MfxmlP6LhiHLkKVI6OQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-2.17.0.tgz", + "integrity": "sha512-44u0b6NP9Go59+mqK69PdsDl7PYfzNpXtcAM/mTsYipRJoUuGU8OwAbZStb1PUhnJJko0j+EZcLy5u7Hm/xDig==", "requires": { "@types/react": "*", - "@visx/group": "1.17.1", - "@visx/point": "1.7.0", - "@visx/scale": "1.14.0", - "@visx/shape": "1.17.1", - "@visx/text": "1.17.1", + "@visx/group": "2.17.0", + "@visx/point": "2.17.0", + "@visx/scale": "2.17.0", + "@visx/shape": "2.17.0", + "@visx/text": "2.17.0", "classnames": "^2.3.1", "prop-types": "^15.6.0" + }, + "dependencies": { + "@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "requires": { + "@types/d3-time": "*" + } + }, + "@visx/curve": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-2.17.0.tgz", + "integrity": "sha512-8Fw2ZalgYbpeoelLqTOmMs/wD8maSKsKS9rRIwmHZ0O0XxY8iG9oVYbD4CLWzf/uFWCY6+qofk4J1g9BWQSXJQ==", + "requires": { + "@types/d3-shape": "^1.3.1", + "d3-shape": "^1.0.6" + } + }, + "@visx/scale": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-2.17.0.tgz", + "integrity": "sha512-ok0RUOSp9VxZzuwo/1I9nsxZxeAdU6wsvIb+cEyMrCuDwm79wzaioSkafAGSb39cKYNrGobFlA3vUd7+JZPCaw==", + "requires": { + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-time": "^2.0.0", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-time": "^2.1.1" + } + }, + "@visx/shape": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-2.17.0.tgz", + "integrity": "sha512-c2uun6f9souLIyUx+WLetG2JSJ4hF3dJqs1yoFZuO5BLNcU35LTCbqvEq10hLPB7TLqkA0s3jWt/rpE4M3S0Mw==", + "requires": { + "@types/d3-path": "^1.0.8", + "@types/d3-shape": "^1.3.1", + "@types/lodash": "^4.14.172", + "@types/react": "*", + "@visx/curve": "2.17.0", + "@visx/group": "2.17.0", + "@visx/scale": "2.17.0", + "classnames": "^2.3.1", + "d3-path": "^1.0.5", + "d3-shape": "^1.2.0", + "lodash": "^4.17.21", + "prop-types": "^15.5.10" + } + } } }, "@visx/bounds": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz", - "integrity": "sha512-ajF6PTgDoZTfwv5J0ZTx1miXY8lk3sGhMVqE3UsMubdTZBlOgeZMT4OmtTPtbCJTBTgw0FD0gd7X3gZ+3X9HgQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-2.17.0.tgz", + "integrity": "sha512-XsoyTAyCm+DZbrPgP3IZFZAcNqBmXFBLSep04TqnrEA3hf16GxIzcpaGe+hAVhPg5yzBdjc7tLk6s0h5F44niA==", "requires": { "@types/react": "*", "@types/react-dom": "*", @@ -25274,52 +27754,93 @@ } }, "@visx/curve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-1.7.0.tgz", - "integrity": "sha512-n0/SHM4YXjke+aEinhHFZPLMxWu3jbqtvqzfGJyibX8OmbDjavk9P+MHfGokUcw0xHy6Ch3YTuwbYuvVw5ny9A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-3.3.0.tgz", + "integrity": "sha512-G1l1rzGWwIs8ka3mBhO/gj8uYK6XdU/3bwRSoiZ+MockMahQFPog0bUkuVgPwwzPSJfsA/E5u53Y/DNesnHQxg==", "requires": { "@types/d3-shape": "^1.3.1", "d3-shape": "^1.0.6" + } + }, + "@visx/event": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/event/-/event-2.17.0.tgz", + "integrity": "sha512-fg2UWo89RgKgWWnnqI+i7EF8Ry+3CdMHTND4lo4DyJvcZZUCOwhxCHMQ4/PHW0EAUfxI51nGadcE1BcEVR5zWw==", + "requires": { + "@types/react": "*", + "@visx/point": "2.17.0" + } + }, + "@visx/glyph": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/glyph/-/glyph-3.3.0.tgz", + "integrity": "sha512-U2r1rFLpim3afKuuAmrbxXGSDCaLwXHmjXxWN8PiIQPMxpS7eaa/V5g2TRd/+x0KCkaf3Ismk4VKMl8ZlrmxIQ==", + "requires": { + "@types/d3-shape": "^1.3.1", + "@types/react": "*", + "@visx/group": "3.3.0", + "classnames": "^2.3.1", + "d3-shape": "^1.2.0", + "prop-types": "^15.6.2" }, "dependencies": { - "d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "@visx/group": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.3.0.tgz", + "integrity": "sha512-yKepDKwJqlzvnvPS0yDuW13XNrYJE4xzT6xM7J++441nu6IybWWwextyap8ey+kU651cYDb+q1Oi6aHvQwyEyw==", "requires": { - "d3-path": "1" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" } } } }, - "@visx/event": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/event/-/event-1.7.0.tgz", - "integrity": "sha512-RbAoKxvy+ildX2dVXC9/ZX94lQXPwjKgtO9jy7COc15knG4zmzsMCDYDC3uLd0+jE2o/+gSaZ/9r52p6zG5+IQ==", + "@visx/gradient": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/gradient/-/gradient-3.3.0.tgz", + "integrity": "sha512-t3vqukahDQsJ64/fcm85woFm2XPpSPMBz92gFvaY4J8EJY3e6rFOg382v5Dm17fgNsLRKJA0Vqo7mUtDe2pWOw==", "requires": { "@types/react": "*", - "@visx/point": "1.7.0" + "prop-types": "^15.5.7" } }, "@visx/grid": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-1.17.1.tgz", - "integrity": "sha512-dse9q3weDqPNmeXK0lGKKPRgGiDuUjJ7Mt7NNonPUyXPctNmv6lJEWZu9HJrXEGiCAVNa8PHJ7Qkns/z+mH88Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-3.5.0.tgz", + "integrity": "sha512-i1pdobTE223ItMiER3q4ojIaZWja3vg46TkS6FotnBZ4c0VRDHSrALQPdi0na+YEgppASWCQ2WrI/vD6mIkhSg==", "requires": { "@types/react": "*", - "@visx/curve": "1.7.0", - "@visx/group": "1.17.1", - "@visx/point": "1.7.0", - "@visx/scale": "1.14.0", - "@visx/shape": "1.17.1", + "@visx/curve": "3.3.0", + "@visx/group": "3.3.0", + "@visx/point": "3.3.0", + "@visx/scale": "3.5.0", + "@visx/shape": "3.5.0", "classnames": "^2.3.1", "prop-types": "^15.6.2" + }, + "dependencies": { + "@visx/group": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.3.0.tgz", + "integrity": "sha512-yKepDKwJqlzvnvPS0yDuW13XNrYJE4xzT6xM7J++441nu6IybWWwextyap8ey+kU651cYDb+q1Oi6aHvQwyEyw==", + "requires": { + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" + } + }, + "@visx/point": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/point/-/point-3.3.0.tgz", + "integrity": "sha512-03eBBIJarkmX79WbeEGTUZwmS5/MUuabbiM9KfkGS9pETBTWkp1DZtEHZdp5z34x5TDQVLSi0rk1Plg3/8RtDg==" + } } }, "@visx/group": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/group/-/group-1.17.1.tgz", - "integrity": "sha512-g8pSqy8TXAisiOzypnVycDynEGlBhfxtVlwDmsbYB+XSFGEjnOheQSDohDI+ia7ek54Mw9uYe05tx5kP1hRMYw==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-2.17.0.tgz", + "integrity": "sha512-60Y2dIKRh3cp/Drpq//wM067ZNrnCcvFCXufPgIihv0Ix8O7oMsYxu3ch4XUMjks+U2IAZQr5Dnc+C9sTQFkhw==", "requires": { "@types/react": "*", "classnames": "^2.3.1", @@ -25327,375 +27848,426 @@ } }, "@visx/legend": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/legend/-/legend-1.17.1.tgz", - "integrity": "sha512-vdB3EPHXYwTyS4g2MB/sVAvq6ddeyzjlTjsM6YNc6Nagwb4uTOuMeKT+t0jgLJejNMOcY7Q8eBjDDWAdiPNV1A==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/legend/-/legend-2.17.0.tgz", + "integrity": "sha512-GslbNTjGXSR1Oxckqj4Pf0iHpPX7m3paGihTkeM+4fbL0xXEQ1GwzpHAXQjgVnbEMpy9J4tJi4WV/SnUuGhD4Q==", "requires": { "@types/react": "*", - "@visx/group": "1.17.1", - "@visx/scale": "1.14.0", + "@visx/group": "2.17.0", + "@visx/scale": "2.17.0", "classnames": "^2.3.1", "prop-types": "^15.5.10" + }, + "dependencies": { + "@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "requires": { + "@types/d3-time": "*" + } + }, + "@visx/scale": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-2.17.0.tgz", + "integrity": "sha512-ok0RUOSp9VxZzuwo/1I9nsxZxeAdU6wsvIb+cEyMrCuDwm79wzaioSkafAGSb39cKYNrGobFlA3vUd7+JZPCaw==", + "requires": { + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-time": "^2.0.0", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-time": "^2.1.1" + } + } } }, "@visx/mock-data": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-1.7.0.tgz", - "integrity": "sha512-v7DGro/4q382ReSWmPCKdRR1AtNUp9ERM129omMaTcf52i8RbSYNfMdxucMaEBIbATI905vYqQKA8S6xopPupQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-3.3.0.tgz", + "integrity": "sha512-yb5R/tAU8fjwRSc5VL1UPYbkD+BoYjXUorblE3/oDcSfFrOvpRMZzSaYCBbZ6jtllge3Ks6QVzwyUUj1/xweqQ==", "requires": { "@types/d3-random": "^2.2.0", "d3-random": "^2.2.2" + }, + "dependencies": { + "d3-random": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", + "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + } } }, "@visx/point": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.7.0.tgz", - "integrity": "sha512-oaoY/HXYHhmpkkeKI4rBPmFtjHWtxSrIhZCVm1ipPoyQp3voJ8L6JD5eUIVmmaUCdUGUGwL1lFLnJiQ2p1Vlwg==" + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.17.0.tgz", + "integrity": "sha512-fUdGQBLGaSVbFTbQ6k+1nPisbqYjTjAdo9FhlwLd3W3uyXN/39Sx2z3N2579sVNBDzmCKdYNQIU0HC+/3Vqo6w==" }, "@visx/responsive": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-1.10.1.tgz", - "integrity": "sha512-7FT2BBmWFkFFqynI9C1NYfVOKT1FsNOm6MwWMqXKA7TMomdBW0wdtQNB1bHvwJvWurM/sNqxcQ/CBED6t9xujQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-2.17.0.tgz", + "integrity": "sha512-3dY2shGbQnoknIRv3Vfnwsy3ZA8Q5Q/rYnTLiokWChYRfNC8NMPoX9mprEeb/gMAxtKjaLn3zcCgd8R+eetxIQ==", "requires": { - "@types/lodash": "^4.14.146", + "@juggle/resize-observer": "^3.3.1", + "@types/lodash": "^4.14.172", "@types/react": "*", - "lodash": "^4.17.10", - "prop-types": "^15.6.1", - "resize-observer-polyfill": "1.5.1" + "lodash": "^4.17.21", + "prop-types": "^15.6.1" } }, "@visx/scale": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-1.14.0.tgz", - "integrity": "sha512-ovbtEOF/d76uGMJ5UZlxdS3t2T8I6md+aIwOXBaq0HdjaCLbe7HLlMyHJKjak/sqBxLAiCGVnechTUpSkfgSQw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-3.5.0.tgz", + "integrity": "sha512-xo3zrXV2IZxrMq9Y9RUVJUpd93h3NO/r/y3GVi5F9AsbOzOhsLIbsPkunhO9mpUSR8LZ9TiumLEBrY+3frRBSg==", "requires": { - "@types/d3-interpolate": "^1.3.1", - "@types/d3-scale": "^3.3.0", - "@types/d3-time": "^2.0.0", - "d3-interpolate": "^1.4.0", - "d3-scale": "^3.3.0", - "d3-time": "^2.1.1" - }, - "dependencies": { - "d3-interpolate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", - "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", - "requires": { - "d3-color": "1" - } - } + "@visx/vendor": "3.5.0" } }, "@visx/shape": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.17.1.tgz", - "integrity": "sha512-rVYFpytPCnV4s5U0za+jQ2jqFzKnmB3c8RP6fuOfF6kKosFPJcOYg9ikvewojARyMBTr1u3XvWV960Da+xyUdQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-3.5.0.tgz", + "integrity": "sha512-DP3t9jBQ7dSE3e6ptA1xO4QAIGxO55GrY/6P+S6YREuQGjZgq20TLYLAsiaoPEzFSS4tp0m12ZTPivWhU2VBTw==", "requires": { "@types/d3-path": "^1.0.8", "@types/d3-shape": "^1.3.1", - "@types/lodash": "^4.14.146", + "@types/lodash": "^4.14.172", "@types/react": "*", - "@visx/curve": "1.7.0", - "@visx/group": "1.17.1", - "@visx/scale": "1.14.0", + "@visx/curve": "3.3.0", + "@visx/group": "3.3.0", + "@visx/scale": "3.5.0", "classnames": "^2.3.1", "d3-path": "^1.0.5", "d3-shape": "^1.2.0", - "lodash": "^4.17.15", + "lodash": "^4.17.21", "prop-types": "^15.5.10" }, "dependencies": { - "d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "@visx/group": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.3.0.tgz", + "integrity": "sha512-yKepDKwJqlzvnvPS0yDuW13XNrYJE4xzT6xM7J++441nu6IybWWwextyap8ey+kU651cYDb+q1Oi6aHvQwyEyw==", "requires": { - "d3-path": "1" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" } } } }, "@visx/text": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/text/-/text-1.17.1.tgz", - "integrity": "sha512-Cx6iH0kVq3YqCfFj7U6bMiKwa/bz4Z3q0vPdxmnVGcPjGZM1ac/y61KFH263e164LJ5jFaTYpPrrFmbZoy8+Vg==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.17.0.tgz", + "integrity": "sha512-Eu6b8SMI+LU4O6H4l/QhCa7c4GtDTQO6jhSYuU70pdTST1Bm74nImPGekG2xDW3uxaLlkb8fDpvXag0Z7v+vlQ==", "requires": { - "@types/lodash": "^4.14.160", + "@types/lodash": "^4.14.172", "@types/react": "*", "classnames": "^2.3.1", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "prop-types": "^15.7.2", "reduce-css-calc": "^1.3.0" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - } - } } }, "@visx/tooltip": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.17.1.tgz", - "integrity": "sha512-YfRgVtKSLTn3iW8CT5+CfTWhSXGeAp01SaPDThtdaUTx89rKv5wb4oyVgeQ5g2ScRYVC8mYj5RzY/pj3RrezFQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-2.17.0.tgz", + "integrity": "sha512-+dMHURP9NqSFZLomMUnoVYjRs+I2qcOw1yYvLtTp/4GUAFRMSUJoSJeuLwng1VBIgCEB95xuQ95NgGID4qzPxA==", "requires": { "@types/react": "*", - "@visx/bounds": "1.7.0", + "@visx/bounds": "2.17.0", "classnames": "^2.3.1", "prop-types": "^15.5.10", "react-use-measure": "^2.0.4" } }, + "@visx/vendor": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-3.5.0.tgz", + "integrity": "sha512-yt3SEZRVmt36+APsCISSO9eSOtzQkBjt+QRxNRzcTWuzwMAaF3PHCCSe31++kkpgY9yFoF+Gfes1TBe5NlETiQ==", + "requires": { + "@types/d3-array": "3.0.3", + "@types/d3-color": "3.1.0", + "@types/d3-delaunay": "6.0.1", + "@types/d3-format": "3.0.1", + "@types/d3-geo": "3.1.0", + "@types/d3-interpolate": "3.0.1", + "@types/d3-scale": "4.0.2", + "@types/d3-time": "3.0.0", + "@types/d3-time-format": "2.1.0", + "d3-array": "3.2.1", + "d3-color": "3.1.0", + "d3-delaunay": "6.0.2", + "d3-format": "3.1.0", + "d3-geo": "3.1.0", + "d3-interpolate": "3.0.1", + "d3-scale": "4.0.2", + "d3-time": "3.1.0", + "d3-time-format": "4.1.0", + "internmap": "2.0.3" + }, + "dependencies": { + "@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==" + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" + }, + "d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } + } + } + }, "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0" + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" + "@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": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" + "@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": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "@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": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@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": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "@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": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "requires": {} }, "@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", - "dev": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "requires": {} }, "@wry/context": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.0.tgz", - "integrity": "sha512-sAgendOXR8dM7stJw3FusRxFHF/ZinU0lffsA2YTyyIOfic86JX02qlPqPVqJNZJPAxFt+2EE8bvq6ZlS0Kf+Q==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz", + "integrity": "sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw==", "requires": { - "tslib": "^2.1.0" + "tslib": "^2.3.0" }, "dependencies": { "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, "@wry/equality": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.4.0.tgz", - "integrity": "sha512-DxN/uawWfhRbgYE55zVCPOoe+jvsQ4m7PT1Wlxjyb/LCCLuU1UsucV2BbCxFAX8bjcSueFBbB5Qfj1Zfe8e7Fw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.2.tgz", + "integrity": "sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA==", "requires": { - "tslib": "^2.1.0" + "tslib": "^2.3.0" }, "dependencies": { "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, "@wry/trie": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.0.tgz", - "integrity": "sha512-Yw1akIogPhAT6XPYsRHlZZIS0tIGmAl9EYXHi2scf7LPKKqdqmow/Hu4kEqP2cJR3EjaU/9L0ZlAjFf3hFxmug==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.1.tgz", + "integrity": "sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw==", "requires": { - "tslib": "^2.1.0" + "tslib": "^2.3.0" }, "dependencies": { "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, @@ -25711,10 +28283,28 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zag-js/dom-query": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", + "integrity": "sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==" + }, + "@zag-js/element-size": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.10.5.tgz", + "integrity": "sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==" + }, + "@zag-js/focus-visible": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.16.0.tgz", + "integrity": "sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==", + "requires": { + "@zag-js/dom-query": "0.16.0" + } + }, "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, "abbrev": { @@ -25724,18 +28314,18 @@ "dev": true }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz", - "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "acorn-globals": { @@ -25756,11 +28346,19 @@ } } }, + "acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "requires": {} + }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -25768,79 +28366,79 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "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-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "requires": { - "string-width": "^3.0.0" + "ajv": "^8.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -25856,330 +28454,241 @@ "type-fest": "^0.11.0" } }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", "dev": true }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, - "apollo-cache-control": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.14.0.tgz", - "integrity": "sha512-qN4BCq90egQrgNnTRMUHikLZZAprf3gbm8rC5Vwmc6ZdLolQ7bFsa769Hqi6Tq/lS31KLsXBLTOsRbfPHph12w==", - "dev": true, - "requires": { - "apollo-server-env": "^3.1.0", - "apollo-server-plugin-base": "^0.13.0" - } - }, "apollo-datasource": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.9.0.tgz", - "integrity": "sha512-y8H99NExU1Sk4TvcaUxTdzfq2SZo6uSj5dyh75XSQvbpH6gdAXIW9MaBcvlNC7n0cVPsidHmOcHOWxJ/pTXGjA==", - "dev": true, - "requires": { - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0" - } - }, - "apollo-graphql": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.2.tgz", - "integrity": "sha512-+c/vqC2LPq3e5kO7MfBxDDiljzLog/THZr9Pd46HVaKAhHUxFL0rJEbT17VhjdOoZGWFWLYG7x9hiN6EQD1xZQ==", - "dev": true, - "requires": { - "core-js-pure": "^3.10.2", - "lodash.sortby": "^4.7.0", - "sha.js": "^2.4.11" - } - }, - "apollo-link": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz", - "integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.2.tgz", + "integrity": "sha512-L5TiS8E2Hn/Yz7SSnWIVbZw0ZfEIXZCa5VUiVxD9P53JvSrf4aStvsFDlGWPvpIdCR+aly2CfoB79B9/JjKFqg==", "dev": true, "requires": { - "apollo-utilities": "^1.3.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.9.3", - "zen-observable-ts": "^0.8.21" - }, - "dependencies": { - "ts-invariant": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", - "integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", - "dev": true, - "requires": { - "tslib": "^1.9.3" - } - } + "@apollo/utils.keyvaluecache": "^1.0.1", + "apollo-server-env": "^4.2.1" } }, "apollo-reporting-protobuf": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.8.0.tgz", - "integrity": "sha512-B3XmnkH6Y458iV6OsA7AhfwvTgeZnFq9nPVjbxmLKnvfkEl8hYADtz724uPa0WeBiD7DSFcnLtqg9yGmCkBohg==", - "dev": true, - "requires": { - "@apollo/protobufjs": "1.2.2" - } - }, - "apollo-server": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.24.0.tgz", - "integrity": "sha512-KYYyBRLvqJaXFk64HfdgPilm8bqc2ON3hwhWmWx2Apsy4PC7paVK4gYFAUh9piPpeVZfF3t7zm+2J+9R47otjA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.4.0.tgz", + "integrity": "sha512-h0u3EbC/9RpihWOmcSsvTW2O6RXVaD/mPEjfrPkxRPTEPWqncsgOoRJw+wih4OqfH3PvTJvoEIf4LwKrUaqWog==", "dev": true, "requires": { - "apollo-server-core": "^2.24.0", - "apollo-server-express": "^2.24.0", - "express": "^4.0.0", - "graphql-subscriptions": "^1.0.0", - "graphql-tools": "^4.0.8", - "stoppable": "^1.1.0" + "@apollo/protobufjs": "1.2.6" }, "dependencies": { - "graphql-tools": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", - "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", + "@apollo/protobufjs": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.6.tgz", + "integrity": "sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw==", "dev": true, "requires": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" + "@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/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" } }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true } } }, - "apollo-server-caching": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.7.0.tgz", - "integrity": "sha512-MsVCuf/2FxuTFVhGLK13B+TZH9tBd2qkyoXKKILIiGcZ5CDUEBO14vIV63aNkMkS1xxvK2U4wBcuuNj/VH2Mkw==", + "apollo-server": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.12.0.tgz", + "integrity": "sha512-wZHLgBoIdGxr/YpPTG5RwNnS+B2y70T/nCegCnU6Yl+H3PXB92OIguLMhdJIZVjukIOhiQT12dNIehqLQ+1hMQ==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "@types/express": "4.17.14", + "apollo-server-core": "^3.12.0", + "apollo-server-express": "^3.12.0", + "express": "^4.17.1" } }, "apollo-server-core": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.2.tgz", - "integrity": "sha512-lrohEjde2TmmDTO7FlOs8x5QQbAS0Sd3/t0TaK2TWaodfzi92QAvIsq321Mol6p6oEqmjm8POIDHW1EuJd7XMA==", - "dev": true, - "requires": { - "@apollographql/apollo-tools": "^0.5.0", - "@apollographql/graphql-playground-html": "1.6.27", - "@apollographql/graphql-upload-8-fork": "^8.1.3", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.12.1.tgz", + "integrity": "sha512-9SF5WAkkV0FZQ2HVUWI9Jada1U0jg7e8NCN9EklbtvaCeUlOLyXyM+KCWuZ7+dqHxjshbtcwylPHutt3uzoNkw==", + "dev": true, + "requires": { + "@apollo/utils.keyvaluecache": "^1.0.1", + "@apollo/utils.logger": "^1.0.0", + "@apollo/utils.usagereporting": "^1.0.0", + "@apollographql/apollo-tools": "^0.5.3", + "@apollographql/graphql-playground-html": "1.6.29", + "@graphql-tools/mock": "^8.1.2", + "@graphql-tools/schema": "^8.0.0", "@josephg/resolvable": "^1.0.0", - "@types/ws": "^7.0.0", - "apollo-cache-control": "^0.14.0", - "apollo-datasource": "^0.9.0", - "apollo-graphql": "^0.9.0", - "apollo-reporting-protobuf": "^0.8.0", - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0", - "apollo-server-errors": "^2.5.0", - "apollo-server-plugin-base": "^0.13.0", - "apollo-server-types": "^0.9.0", - "apollo-tracing": "^0.15.0", + "apollo-datasource": "^3.3.2", + "apollo-reporting-protobuf": "^3.4.0", + "apollo-server-env": "^4.2.1", + "apollo-server-errors": "^3.3.1", + "apollo-server-plugin-base": "^3.7.2", + "apollo-server-types": "^3.8.0", "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.0.0", - "graphql-extensions": "^0.15.0", + "fast-json-stable-stringify": "^2.1.0", "graphql-tag": "^2.11.0", - "graphql-tools": "^4.0.8", - "loglevel": "^1.6.7", + "loglevel": "^1.6.8", "lru-cache": "^6.0.0", + "node-abort-controller": "^3.0.1", "sha.js": "^2.4.11", - "subscriptions-transport-ws": "^0.9.19", - "uuid": "^8.0.0" + "uuid": "^9.0.0", + "whatwg-mimetype": "^3.0.0" }, "dependencies": { - "graphql-tools": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", - "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", + "@graphql-tools/merge": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.3.1.tgz", + "integrity": "sha512-BMm99mqdNZbEYeTPK3it9r9S6rsZsQKtlqJsSBknAclXq2pGEfOxjcIZi+kBSkHZKPKCRrYDd5vY0+rUmIHVLg==", "dev": true, "requires": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } + "@graphql-tools/utils": "8.9.0", + "tslib": "^2.4.0" + } + }, + "@graphql-tools/schema": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.5.1.tgz", + "integrity": "sha512-0Esilsh0P/qYcB5DKQpiKeQs/jevzIadNTaT0jeWklPMwNbT7yMX4EqZany7mbeRRlSRwMzNzL5olyFdffHBZg==", + "dev": true, + "requires": { + "@graphql-tools/merge": "8.3.1", + "@graphql-tools/utils": "8.9.0", + "tslib": "^2.4.0", + "value-or-promise": "1.0.11" + } + }, + "@graphql-tools/utils": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.9.0.tgz", + "integrity": "sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg==", + "dev": true, + "requires": { + "tslib": "^2.4.0" } + }, + "tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true + }, + "value-or-promise": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", + "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==", + "dev": true + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true } } }, "apollo-server-env": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-3.1.0.tgz", - "integrity": "sha512-iGdZgEOAuVop3vb0F2J3+kaBVi4caMoxefHosxmgzAbbSpvWehB8Y1QiSyyMeouYC38XNVk5wnZl+jdGSsWsIQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz", + "integrity": "sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g==", "dev": true, "requires": { - "node-fetch": "^2.6.1", - "util.promisify": "^1.0.0" + "node-fetch": "^2.6.7" } }, "apollo-server-errors": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz", - "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz", + "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==", "dev": true, "requires": {} }, "apollo-server-express": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.2.tgz", - "integrity": "sha512-A2gF2e85vvDugPlajbhr0A14cDFDIGX0mteNOJ8P3Z3cIM0D4hwrWxJidI+SzobefDIyIHu1dynFedJVhV0euQ==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.12.0.tgz", + "integrity": "sha512-m8FaGPUfDOEGSm7QRWRmUUGjG/vqvpQoorkId9/FXkC57fz/A59kEdrzkMt9538Xgsa5AV+X4MEWLJhTvlW3LQ==", "dev": true, "requires": { - "@apollographql/graphql-playground-html": "1.6.27", "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.0", - "@types/cors": "2.8.10", - "@types/express": "^4.17.12", - "@types/express-serve-static-core": "^4.17.21", + "@types/body-parser": "1.19.2", + "@types/cors": "2.8.12", + "@types/express": "4.17.14", + "@types/express-serve-static-core": "4.17.31", "accepts": "^1.3.5", - "apollo-server-core": "^2.25.2", - "apollo-server-types": "^0.9.0", - "body-parser": "^1.18.3", + "apollo-server-core": "^3.12.0", + "apollo-server-types": "^3.8.0", + "body-parser": "^1.19.0", "cors": "^2.8.5", - "express": "^4.17.1", - "graphql-subscriptions": "^1.0.0", - "graphql-tools": "^4.0.8", - "parseurl": "^1.3.2", - "subscriptions-transport-ws": "^0.9.19", - "type-is": "^1.6.16" - }, - "dependencies": { - "graphql-tools": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", - "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", - "dev": true, - "requires": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } + "parseurl": "^1.3.3" } }, "apollo-server-plugin-base": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.13.0.tgz", - "integrity": "sha512-L3TMmq2YE6BU6I4Tmgygmd0W55L+6XfD9137k+cWEBFu50vRY4Re+d+fL5WuPkk5xSPKd/PIaqzidu5V/zz8Kg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.2.tgz", + "integrity": "sha512-wE8dwGDvBOGehSsPTRZ8P/33Jan6/PmL0y0aN/1Z5a5GcbFhDaaJCjK5cav6npbbGL2DPKK0r6MPXi3k3N45aw==", "dev": true, "requires": { - "apollo-server-types": "^0.9.0" + "apollo-server-types": "^3.8.0" } }, "apollo-server-types": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.9.0.tgz", - "integrity": "sha512-qk9tg4Imwpk732JJHBkhW0jzfG0nFsLqK2DY6UhvJf7jLnRePYsPxWfPiNkxni27pLE2tiNlCwoDFSeWqpZyBg==", - "dev": true, - "requires": { - "apollo-reporting-protobuf": "^0.8.0", - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0" - } - }, - "apollo-tracing": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.15.0.tgz", - "integrity": "sha512-UP0fztFvaZPHDhIB/J+qGuy6hWO4If069MGC98qVs0I8FICIGu4/8ykpX3X3K6RtaQ56EDAWKykCxFv4ScxMeA==", - "dev": true, - "requires": { - "apollo-server-env": "^3.1.0", - "apollo-server-plugin-base": "^0.13.0" - } - }, - "apollo-utilities": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz", - "integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.8.0.tgz", + "integrity": "sha512-ZI/8rTE4ww8BHktsVpb91Sdq7Cb71rdSkXELSwdSR0eXu600/sY+1UXhTWdiJvk+Eq5ljqoHLwLbY2+Clq2b9A==", "dev": true, "requires": { - "@wry/equality": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.10.0" - }, - "dependencies": { - "@wry/equality": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz", - "integrity": "sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==", - "dev": true, - "requires": { - "tslib": "^1.9.3" - } - }, - "ts-invariant": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", - "integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", - "dev": true, - "requires": { - "tslib": "^1.9.3" - } - } + "@apollo/utils.keyvaluecache": "^1.0.1", + "@apollo/utils.logger": "^1.0.0", + "apollo-reporting-protobuf": "^3.4.0", + "apollo-server-env": "^4.2.1" } }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -26190,22 +28699,25 @@ } }, "aria-hidden": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz", - "integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", "requires": { - "tslib": "^1.0.0" + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } } }, "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true }, "arr-diff": { "version": "4.0.0", @@ -26231,14 +28743,16 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" } }, "array-union": { @@ -26263,39 +28777,32 @@ "dev": true }, "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "es-abstract": "^1.19.0" } }, "array.prototype.flatmap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", - "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, "assign-symbols": { @@ -26305,38 +28812,18 @@ "dev": true }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "async-retry": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", - "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "dev": true, "requires": { - "retry": "0.12.0" + "retry": "0.13.1" } }, "asynckit": { @@ -26345,29 +28832,20 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } }, "babel-jest": { "version": "26.6.3", @@ -26437,37 +28915,15 @@ } }, "babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", + "loader-utils": "^2.0.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } } }, "babel-plugin-dynamic-import-node": { @@ -26505,26 +28961,63 @@ } }, "babel-plugin-macros": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.0.1.tgz", - "integrity": "sha512-CKt4+Oy9k2wiN+hT1uZzOw7d8zb1anbQpf7KLwaaXRCi/4pzKdFKHf7v5mvoPmjkmxshh7eKZQuRop06r5WP4w==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "requires": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "babel-plugin-polyfill-corejs2": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", + "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", "dev": true, "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz", + "integrity": "sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -26545,11 +29038,6 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -26616,32 +29104,18 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "devOptional": true + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -26649,9 +29123,9 @@ "dev": true }, "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "bl": { @@ -26674,34 +29148,63 @@ } }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", + "bytes": "~3.1.2", + "content-type": "~1.0.5", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + } } }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "bonjour-service": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.12.tgz", + "integrity": "sha512-pMmguXYCu63Ug37DluMKEHdxc+aaIf/ay4YbF8Gxtba+9d3u+rmEWy61VK3Z3hp8Rskok3BunHYnG0dUHAsblw==", "dev": true, "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", + "array-flatten": "^2.1.2", "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.4" }, "dependencies": { "array-flatten": { @@ -26718,89 +29221,10 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -26808,12 +29232,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -26823,15 +29247,16 @@ "dev": true }, "browserslist": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", - "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "devOptional": true, "requires": { - "caniuse-lite": "^1.0.30001173", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.634", - "escalade": "^3.1.1", - "node-releases": "^1.1.69" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, "bser": { @@ -26856,7 +29281,7 @@ "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true }, "buffer-from": { @@ -26865,25 +29290,10 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "busboy": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", - "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", - "dev": true, - "requires": { - "dicer": "0.3.0" - } - }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cache-base": { "version": "1.0.1", @@ -26902,46 +29312,34 @@ "unset-value": "^1.0.0" } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } + "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-bind": { + "call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "callsites": { @@ -26968,15 +29366,16 @@ } }, "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", + "devOptional": true }, "caniuse-lite": { - "version": "1.0.30001246", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz", - "integrity": "sha512-Tc+ff0Co/nFNbLOrziBXmMVtpt9S2c2Y+Z9Nk9Khj09J+0zR9ejvIW5qkZAErCbOrVODCx/MN+GpB5FNBs5GFA==" + "version": "1.0.30001768", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", + "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", + "devOptional": true }, "capture-exit": { "version": "2.0.0", @@ -26987,16 +29386,11 @@ "rsvp": "^4.8.4" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -27009,12 +29403,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -27031,21 +29419,6 @@ "readdirp": "~3.5.0" }, "dependencies": { - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -27107,9 +29480,9 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", + "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -27133,12 +29506,6 @@ "del": "^4.1.1" } }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -27155,21 +29522,14 @@ "dev": true }, "cli-table": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.4.tgz", - "integrity": "sha512-1vinpnX/ZERcmE443i3SZTmU5DF0rPO9DrL4I2iVAllhxzCM9SzPlHnz19fsZB78htkKZvYBvj6SZ6vXnaxmTA==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", "dev": true, "requires": { - "chalk": "^2.4.1", - "string-width": "^4.2.0" + "colors": "1.4.0" } }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -27198,15 +29558,6 @@ "shallow-clone": "^3.0.0" } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -27233,6 +29584,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -27240,12 +29592,25 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" }, "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true }, "combined-stream": { "version": "1.0.8", @@ -27257,9 +29622,9 @@ } }, "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true }, "commondir": { @@ -27278,38 +29643,40 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, "requires": { "mime-db": ">= 1.43.0 < 2" } }, "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true + "negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "compute-scroll-into-view": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz", - "integrity": "sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" }, "concat-map": { "version": "0.0.1", @@ -27317,44 +29684,31 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "1.7.0", @@ -27365,9 +29719,9 @@ } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -27375,9 +29729,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, "copy-descriptor": { @@ -27387,36 +29741,90 @@ "dev": true }, "copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", "requires": { "toggle-selection": "^1.0.6" } }, - "core-js-compat": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", - "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", + "copy-webpack-plugin": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "requires": { - "browserslist": "^4.16.1", - "semver": "7.0.0" + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" }, "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + }, + "serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true } } }, - "core-js-pure": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.2.tgz", - "integrity": "sha512-DQxdEKm+zFsnON7ZGOgUAQXBt1UJJ01tOzN/HgQ7cNf0oEHW1tcBLfCQQd1q6otdLu5gAdvKYxKHAoXGwE/kiQ==", - "dev": true + "core-js-compat": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "dev": true, + "requires": { + "browserslist": "^4.22.2" + } }, "core-util-is": { "version": "1.0.2", @@ -27438,7 +29846,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -27447,16 +29854,10 @@ "yaml": "^1.10.0" } }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -27464,37 +29865,6 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -27504,21 +29874,22 @@ } }, "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", + "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", "dev": true, "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "boolbase": "^1.0.0", + "css-what": "^5.0.0", + "domhandler": "^4.2.0", + "domutils": "^2.6.0", + "nth-check": "^2.0.0" } }, "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", + "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", "dev": true }, "css.escape": { @@ -27530,7 +29901,7 @@ "cssfilter": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", "dev": true }, "cssom": { @@ -27557,56 +29928,75 @@ } }, "csstype": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", - "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "d3": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.2.0.tgz", - "integrity": "sha512-aH+kx55J8vRBh4K4k9GN4EbNO3QnZsXy4XBfrnr4fL2gQuszUAPQU3fV2oObO2iSpreRH/bG/wfvO+hDu2+e9w==", - "requires": { - "d3-array": "2", - "d3-axis": "2", - "d3-brush": "2", - "d3-chord": "2", - "d3-color": "2", - "d3-contour": "2", - "d3-delaunay": "5", - "d3-dispatch": "2", - "d3-drag": "2", - "d3-dsv": "2", - "d3-ease": "2", - "d3-fetch": "2", - "d3-force": "2", - "d3-format": "2", - "d3-geo": "2", - "d3-hierarchy": "2", - "d3-interpolate": "2", - "d3-path": "2", - "d3-polygon": "2", - "d3-quadtree": "2", - "d3-random": "2", - "d3-scale": "3", - "d3-scale-chromatic": "2", - "d3-selection": "2", - "d3-shape": "2", - "d3-time": "2", - "d3-time-format": "3", - "d3-timer": "2", - "d3-transition": "2", - "d3-zoom": "2" - }, - "dependencies": { - "d3-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", - "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.0.tgz", + "integrity": "sha512-a5rNemRadWkEfqnY5NsD4RdCP9vn8EIJ4I5Rl14U0uKH1SXqcNmk/h9aGaAF1O98lz6L9M0IeUcuPa9GUYbI5A==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "dependencies": { + "d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "requires": { + "internmap": "1 - 2" + } }, "d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } } } }, @@ -27616,129 +30006,147 @@ "integrity": "sha512-dYWhEvg1L2+osFsSqNHpXaPQNugLT4JfyvbLE046I2PDcgYGFYc0w24GSJwbmcjjZYOPC3PNP2S782bWUM967Q==" }, "d3-axis": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.0.0.tgz", - "integrity": "sha512-9nzB0uePtb+u9+dWir+HTuEAKJOEUYJoEwbJPsZ1B4K3iZUgzJcSENQ05Nj7S4CIfbZZ8/jQGoUzGKFznBhiiQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" }, "d3-brush": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-2.1.0.tgz", - "integrity": "sha512-cHLLAFatBATyIKqZOkk/mDHUbzne2B3ZwxkzMHvFTCZCmLaXDpZRihQSn8UNXTkGD/3lb/W2sQz0etAftmHMJQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "requires": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" } }, "d3-chord": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz", - "integrity": "sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "requires": { - "d3-path": "1 - 2" + "d3-path": "1 - 3" } }, "d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" }, "d3-contour": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz", - "integrity": "sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", "requires": { - "d3-array": "2" + "d3-array": "^3.2.0" + }, + "dependencies": { + "d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "requires": { + "internmap": "1 - 2" + } + } } }, "d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", "requires": { - "delaunator": "4" + "delaunator": "5" } }, "d3-dispatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", - "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" }, "d3-drag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz", - "integrity": "sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "requires": { - "d3-dispatch": "1 - 2", - "d3-selection": "2" + "d3-dispatch": "1 - 3", + "d3-selection": "3" } }, "d3-dsv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", - "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "requires": { - "commander": "2", - "iconv-lite": "0.4", + "commander": "7", + "iconv-lite": "0.6", "rw": "1" }, "dependencies": { "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } } } }, "d3-ease": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz", - "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" }, "d3-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz", - "integrity": "sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "requires": { - "d3-dsv": "1 - 2" + "d3-dsv": "1 - 3" } }, "d3-force": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", - "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "requires": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" } }, "d3-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", - "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" }, "d3-geo": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.1.tgz", - "integrity": "sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "requires": { - "d3-array": ">=2.5" + "d3-array": "2.5.0 - 3" } }, "d3-hierarchy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz", - "integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" }, "d3-interpolate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", - "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "requires": { - "d3-color": "1 - 2" + "d3-color": "1 - 3" } }, "d3-path": { @@ -27747,52 +30155,62 @@ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, "d3-polygon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", - "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" }, "d3-quadtree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", - "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" }, "d3-random": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", - "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" }, "d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "requires": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "dependencies": { + "d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "requires": { + "internmap": "1 - 2" + } + } } }, "d3-scale-chromatic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", - "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", "requires": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" } }, "d3-selection": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", - "integrity": "sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" }, "d3-shape": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.0.0.tgz", - "integrity": "sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "requires": { - "d3-path": "1 - 2" + "d3-path": "1" } }, "d3-time": { @@ -27804,49 +30222,40 @@ } }, "d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "requires": { - "d3-time": "1 - 2" + "d3-time": "1 - 3" } }, "d3-timer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", - "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" }, "d3-transition": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-2.0.0.tgz", - "integrity": "sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "requires": { - "d3-color": "1 - 2", - "d3-dispatch": "1 - 2", - "d3-ease": "1 - 2", - "d3-interpolate": "1 - 2", - "d3-timer": "1 - 2" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" } }, "d3-zoom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz", - "integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==", - "requires": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "requires": { - "assert-plus": "^1.0.0" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" } }, "data-urls": { @@ -27861,9 +30270,9 @@ } }, "date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "dev": true }, "debounce": { @@ -27892,39 +30301,20 @@ "dev": true }, "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } + "deeks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/deeks/-/deeks-2.5.1.tgz", + "integrity": "sha512-fqrBeUz7f1UqaXDRzVB5RG2EfPk15HJRrb2pMZj8mLlSTtz4tRPsK5leFOskoHFPuyZ6+7aRM9j657fvXLkJ7Q==" }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==" }, "deep-is": { "version": "0.1.3", @@ -27938,13 +30328,58 @@ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" }, "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", "dev": true, "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" + "execa": "^5.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "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" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } } }, "defaults": { @@ -27956,19 +30391,31 @@ "clone": "^1.0.2" } }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "define-property": { @@ -28028,9 +30475,12 @@ } }, "delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "requires": { + "robust-predicates": "^3.0.0" + } }, "delayed-stream": { "version": "1.0.0", @@ -28041,18 +30491,13 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "deprecated-decorator": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", - "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-newline": { "version": "3.1.0", @@ -28071,21 +30516,16 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, - "dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "requires": { - "streamsearch": "0.1.2" + "asap": "^2.0.0", + "wrappy": "1" } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -28108,23 +30548,18 @@ "dev": true }, "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", "dev": true, "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" + "@leichtgewicht/ip-codec": "^2.0.1" } }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "^1.0.0" - } + "doc-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-3.0.2.tgz", + "integrity": "sha512-VRlA2OKSjTbHWj6wmSanxJ338fE/YN8pqmZ0FIWK5JWkIJMFRc4KmD35JtOrnjvVG0WrzOtDDNHx1lN1tkb+lA==" }, "doctrine": { "version": "3.0.0", @@ -28136,9 +30571,9 @@ } }, "dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz", + "integrity": "sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==", "dev": true }, "dom-converter": { @@ -28151,21 +30586,14 @@ } }, "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, "requires": { "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", - "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", - "dev": true - } } }, "dom-walk": { @@ -28175,9 +30603,9 @@ "dev": true }, "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, "domexception": { @@ -28198,64 +30626,70 @@ } }, "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "^2.2.0" } }, "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" } }, "dot-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", - "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", - "dev": true, - "requires": { - "no-case": "^3.0.3", - "tslib": "^1.10.0" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "requires": { - "is-obj": "^2.0.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } } }, "dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", "dev": true }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -28271,9 +30705,10 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.642", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz", - "integrity": "sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ==" + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "devOptional": true }, "emittery": { "version": "0.7.2", @@ -28308,21 +30743,13 @@ } }, "enhanced-resolve": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.0.tgz", - "integrity": "sha512-EENz3E701+77g0wfbOITeI8WLPNso2kQNMBIBEi/TH/BEa9YXtS01X7sIEk5XXsfFq1jNkhIpu08hBPH1TRLIQ==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", - "tapable": "^2.0.0" - }, - "dependencies": { - "tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", - "dev": true - } + "tapable": "^2.3.0" } }, "enquirer": { @@ -28335,26 +30762,17 @@ } }, "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -28371,27 +30789,79 @@ } }, "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" } }, "es-to-primitive": { @@ -28405,16 +30875,44 @@ "is-symbol": "^1.0.2" } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true }, "escape-html": { "version": "1.0.3", @@ -28424,7 +30922,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escodegen": { "version": "2.0.0", @@ -28494,29 +30993,32 @@ } }, "eslint": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz", - "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -28524,7 +31026,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -28533,11 +31035,20 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -28573,14 +31084,20 @@ "dev": true }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" } }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, "eslint-visitor-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", @@ -28588,12 +31105,12 @@ "dev": true }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "has-flag": { @@ -28615,10 +31132,13 @@ "dev": true }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "supports-color": { "version": "7.2.0", @@ -28630,45 +31150,66 @@ } }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, "eslint-config-prettier": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", - "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", - "dev": true - }, - "eslint-config-standard": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.2.tgz", - "integrity": "sha512-fx3f1rJDsl9bY7qzyX8SAtP8GBSk6MfXFaTfaGgk12aAYW4gJSyRm7dM790L6cbXv63fvjY4XeSzXnb4WM+SKw==", - "dev": true + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", - "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", "dev": true, "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } } }, "eslint-module-utils": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", - "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", "dev": true, "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" + "debug": "^3.2.7", + "find-up": "^2.1.0" }, "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -28688,6 +31229,12 @@ "path-exists": "^3.0.0" } }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -28711,121 +31258,70 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } } } }, - "eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - } - }, "eslint-plugin-import": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", - "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, "requires": { - "array-includes": "^3.1.1", - "array.prototype.flat": "^1.2.3", - "contains-path": "^0.1.0", + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.4", - "eslint-module-utils": "^2.6.0", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.1", - "read-pkg-up": "^2.0.0", - "resolve": "^1.17.0", - "tsconfig-paths": "^3.9.0" + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" }, "dependencies": { "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "^2.0.2" } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true } } }, "eslint-plugin-jest": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz", - "integrity": "sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "^4.0.1" - } - }, - "eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "version": "27.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz", + "integrity": "sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==", "dev": true, "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "@typescript-eslint/utils": "^5.10.0" } }, - "eslint-plugin-promise": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", - "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", - "dev": true - }, "eslint-plugin-react": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", - "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", "dev": true, "requires": { - "array-includes": "^3.1.1", - "array.prototype.flatmap": "^1.2.3", + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", "doctrine": "^2.1.0", - "has": "^1.0.3", + "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "object.entries": "^1.1.2", - "object.fromentries": "^2.0.2", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "resolve": "^1.18.1", - "string.prototype.matchall": "^4.0.2" + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" }, "dependencies": { "doctrine": { @@ -28836,20 +31332,37 @@ "requires": { "esutils": "^2.0.2" } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true } } }, "eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true - }, - "eslint-plugin-standard": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", - "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", - "dev": true + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -28859,25 +31372,6 @@ "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" - }, - "dependencies": { - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - } } }, "eslint-utils": { @@ -28895,14 +31389,21 @@ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "optional": true + }, "espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", + "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, "dependencies": { @@ -28921,18 +31422,35 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -28952,12 +31470,12 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "events": { @@ -28966,15 +31484,6 @@ "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, - "eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "dev": true, - "requires": { - "original": "^1.0.0" - } - }, "exec-sh": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", @@ -28997,9 +31506,9 @@ }, "dependencies": { "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -29123,48 +31632,65 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.0.tgz", + "integrity": "sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", + "depd": "2.0.0", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.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.3.0", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "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" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -29186,17 +31712,6 @@ } } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -29262,12 +31777,6 @@ } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, "faker": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", @@ -29277,26 +31786,27 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" } }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -29305,9 +31815,15 @@ "dev": true }, "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true }, "fastest-levenshtein": { @@ -29317,18 +31833,18 @@ "dev": true }, "fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" } }, "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, "requires": { "websocket-driver": ">=0.5.1" @@ -29343,51 +31859,48 @@ "bser": "2.1.1" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, - "filter-console": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/filter-console/-/filter-console-0.1.1.tgz", - "integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==", - "dev": true - }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" + }, + "dependencies": { + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, "find-cache-dir": { @@ -29451,30 +31964,20 @@ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "dependencies": { "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -29483,37 +31986,39 @@ } }, "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, - "fn-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz", - "integrity": "sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==" - }, "focus-lock": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.8.1.tgz", - "integrity": "sha512-/LFZOIo82WDsyyv7h7oc0MJF9ACOvDRdx9rWPZ2pgMfNWu/z8hQDBtOchuB/0BVLmuFOZjV02YwUVzNsWx/EzA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", "requires": { - "tslib": "^1.9.3" + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } } }, "follow-redirects": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", - "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true }, "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "requires": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" } }, "for-in": { @@ -29522,48 +32027,67 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "dev": true, + "requires": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" + } }, "formik": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.0.tgz", - "integrity": "sha512-l47RfvejhfHNh8rTRVaCaPfx8nyeYDSTLaEqRvLX4qkWnrrq9ByGVCWggVR+0TVtzc5Ub1gLUuVu9UKuGwfhjA==", + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", + "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==", "requires": { "deepmerge": "^2.1.1", "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.14", - "lodash-es": "^4.17.14", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "react-fast-compare": "^2.0.1", - "scheduler": "^0.18.0", "tiny-warning": "^1.0.2", "tslib": "^1.10.0" } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fragment-cache": { "version": "0.2.1", @@ -29612,26 +32136,14 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, - "fs-capacitor": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz", - "integrity": "sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==", + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", "dev": true }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - } - }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -29652,9 +32164,21 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } }, "functional-red-black-tree": { "version": "1.0.1", @@ -29662,16 +32186,17 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "fuzzaldrin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", - "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=", + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "devOptional": true }, "get-caller-file": { "version": "2.0.5", @@ -29680,14 +32205,20 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "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-nonce": { @@ -29701,6 +32232,15 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -29710,39 +32250,40 @@ "pump": "^3.0.0" } }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -29764,15 +32305,6 @@ "process": "^0.11.10" } }, - "global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dev": true, - "requires": { - "ini": "1.3.7" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -29799,77 +32331,52 @@ } } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "graphql": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz", - "integrity": "sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==" - }, - "graphql-extensions": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.15.0.tgz", - "integrity": "sha512-bVddVO8YFJPwuACn+3pgmrEg6I8iBuYLuwvxiE+lcQQ7POotVZxm2rgGw0PvVYmWWf3DT7nTVDZ5ROh/ALp8mA==", - "dev": true, - "requires": { - "@apollographql/apollo-tools": "^0.5.0", - "apollo-server-env": "^3.1.0", - "apollo-server-types": "^0.9.0" - } + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==" }, "graphql-relay": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.6.0.tgz", - "integrity": "sha512-OVDi6C9/qOT542Q3KxZdXja3NrDvqzbihn1B44PH8P/c5s0Q90RyQwT6guhGqXqbYEH6zbeLJWjQqiYvcg2vVw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.0.tgz", + "integrity": "sha512-44yBuw2/DLNEiMypbNZBt1yMDbBmyVPVesPywnteGGALiBmdyy1JP8jSg8ClLePg8ZZxk0O4BLhd1a6U/1jDOQ==", "dev": true, - "requires": { - "prettier": "^1.16.0" - }, - "dependencies": { - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - } - } + "requires": {} }, "graphql-subscriptions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz", - "integrity": "sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz", + "integrity": "sha512-s6k2b8mmt9gF9pEfkxsaO1lTxaySfKoEJzEfmwguBbQ//Oq23hIXCfR1hm4kdh5hnR20RdwB+s3BCb+0duHSZA==", "dev": true, "requires": { "iterall": "^1.3.0" } }, "graphql-tag": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz", - "integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==" + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } }, "growly": { "version": "1.3.0", @@ -29884,22 +32391,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -29909,21 +32400,39 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } }, "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } }, "has-value": { "version": "1.0.0", @@ -29977,11 +32486,13 @@ } } }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } }, "he": { "version": "1.2.0", @@ -29994,19 +32505,6 @@ "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -30075,9 +32573,9 @@ } }, "html-entities": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", - "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "html-escaper": { @@ -30087,70 +32585,53 @@ "dev": true }, "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", - "param-case": "^3.0.3", + "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^4.6.3" + "terser": "^5.10.0" }, "dependencies": { "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true } } }, "html-webpack-plugin": { - "version": "5.0.0-beta.6", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.0.0-beta.6.tgz", - "integrity": "sha512-wjdhOnJlo5c8uN3OahRm2eaLKL8gEQ4ZNOkwQc8BStyudpFLTsg28m6wbf00keXiRPesk2Pad9CYeKpxbffApg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, "requires": { - "@types/html-minifier-terser": "^5.0.0", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^2.0.0", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", "tapable": "^2.0.0" } }, "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "dev": true, "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - } + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -30158,17 +32639,40 @@ "dev": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, "http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -30180,132 +32684,72 @@ "requires-port": "^1.0.0" } }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "ms": "2.1.2" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "human-signals": { @@ -30329,9 +32773,9 @@ "dev": true }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "ignore-by-default": { @@ -30341,20 +32785,14 @@ "dev": true }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -30435,113 +32873,42 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { - "es-abstract": "^1.17.0-next.1", + "get-intrinsic": "^1.1.0", "has": "^1.0.3", - "side-channel": "^1.0.2" + "side-channel": "^1.0.4" } }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, + "intro.js": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.2.0.tgz", + "integrity": "sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ==" + }, + "intro.js-react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/intro.js-react/-/intro.js-react-1.0.0.tgz", + "integrity": "sha512-zR8pbTyX20RnCZpJMc0nuHBpsjcr1wFkj3ZookV6Ly4eE/LGpFTQwPsaA61Cryzwiy/tTFsusf4hPU9NpI9UOg==", + "requires": {} + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -30550,29 +32917,11 @@ "loose-envify": "^1.0.0" } }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true - }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -30593,50 +32942,32 @@ } } }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "is-alphanumerical": { + "is-bigint": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" + "has-bigints": "^1.0.1" } }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, - "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true - }, "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "^2.0.0" } }, "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-buffer": { @@ -30646,9 +32977,9 @@ "dev": true }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-ci": { @@ -30661,9 +32992,9 @@ } }, "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "requires": { "has": "^1.0.3" } @@ -30694,12 +33025,6 @@ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -30723,8 +33048,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true + "dev": true }, "is-extendable": { "version": "0.1.1", @@ -30751,48 +33075,29 @@ "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "dependencies": { - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - } - } - }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true + "is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==" }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, "is-number": { @@ -30802,15 +33107,18 @@ "dev": true }, "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "is-observable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz", + "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==", "dev": true }, "is-path-cwd": { @@ -30837,6 +33145,12 @@ "path-is-inside": "^1.0.2" } }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -30847,19 +33161,28 @@ } }, "is-potential-custom-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", - "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" } }, "is-stream": { @@ -30869,10 +33192,13 @@ "dev": true }, "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-symbol": { "version": "1.0.3", @@ -30883,12 +33209,30 @@ "has-symbols": "^1.0.1" } }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.16" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -30900,21 +33244,15 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "optional": true, "requires": { "is-docker": "^2.0.0" } }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "isexe": { "version": "2.0.0", @@ -30937,12 +33275,6 @@ "unfetch": "^4.2.0" } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, "istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", @@ -30962,9 +33294,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -31044,7 +33376,17 @@ "iterall": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "dev": true + }, + "jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2" + } }, "jest": { "version": "26.6.3", @@ -31403,55 +33745,10 @@ } }, "jest-emotion": { - "version": "10.0.32", - "resolved": "https://registry.npmjs.org/jest-emotion/-/jest-emotion-10.0.32.tgz", - "integrity": "sha512-hW3IwWc47qRuxnGsWFGY6uIMX8F4YBzq+Qci3LAYUCUqUBNP+1DU1L5Nudo9Ry0NHVFOqDnDeip1p2UR0kVMwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.5.5", - "@types/jest": "^23.0.2", - "chalk": "^2.4.1", - "css": "^2.2.1" - }, - "dependencies": { - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - } - } + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/jest-emotion/-/jest-emotion-11.0.0.tgz", + "integrity": "sha512-nw+iYPcHM+H5huUIsDh53uEIp6SrN5v+MOxa7qyXReae9foCTACDkFuF/1Hfkik/R8mDMir7q5QEiVMRJ0/f2Q==", + "dev": true }, "jest-environment-jsdom": { "version": "26.6.2", @@ -32184,9 +34481,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -32400,14 +34697,14 @@ } }, "jest-worker": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", - "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "supports-color": "^8.0.0" }, "dependencies": { "has-flag": { @@ -32417,9 +34714,9 @@ "dev": true }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -32427,35 +34724,40 @@ } } }, + "jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true + }, + "js-sha256": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz", + "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, "jsdom": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.2.tgz", - "integrity": "sha512-JxNtPt9C1ut85boCbJmffaQ06NBnzkQY/MWO3YxPW8IWS38A26z+B1oBvA9LwKrytewdfymnhi4UNH3/RAgZrg==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", "dev": true, "requires": { "abab": "^2.0.5", - "acorn": "^8.1.0", + "acorn": "^8.2.4", "acorn-globals": "^6.0.0", "cssom": "^0.4.4", "cssstyle": "^2.3.0", @@ -32463,12 +34765,13 @@ "decimal.js": "^10.2.1", "domexception": "^2.0.1", "escodegen": "^2.0.0", + "form-data": "3.0.4", "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.0", "parse5": "6.0.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.9", "saxes": "^5.0.1", "symbol-tree": "^3.2.4", "tough-cookie": "^4.0.0", @@ -32478,8 +34781,23 @@ "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", "whatwg-url": "^8.5.0", - "ws": "^7.4.4", + "ws": "^7.4.6", "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + } + } } }, "jsesc": { @@ -32487,22 +34805,19 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "json-2-csv": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-3.17.1.tgz", + "integrity": "sha512-i6QynVy42GGMgY8fYde0mp6nYteptvk8oJsphOLiT3CITzw7NBBAiRwHV35kDOBii/elDQe1HCWLqaBPJ3istQ==", + "requires": { + "deeks": "2.5.1", + "doc-path": "3.0.2" + } }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema-traverse": { "version": "0.4.1", @@ -32516,52 +34831,22 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "devOptional": true }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dev": true, "requires": { "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "ms": { @@ -32569,21 +34854,18 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "jsx-ast-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", @@ -32595,41 +34877,26 @@ } }, "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "dev": true, "requires": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "dev": true, "requires": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -32642,20 +34909,21 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "launch-editor": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "dev": true, "requires": { - "package-json": "^6.3.0" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true + "devOptional": true }, "levn": { "version": "0.4.1", @@ -32672,45 +34940,16 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "loader-runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz", - "integrity": "sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true }, "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -32718,94 +34957,37 @@ "json5": "^2.1.2" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==" }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", - "dev": true - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", - "dev": true - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", - "dev": true - }, "lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", @@ -32814,7 +34996,7 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, "lodash.startswith": { @@ -32822,6 +35004,12 @@ "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -32883,9 +35071,9 @@ } }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", "dev": true }, "long": { @@ -32919,12 +35107,6 @@ } } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -32950,23 +35132,17 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "make-plural": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.2.tgz", - "integrity": "sha512-8iTuFioatnTTmb/YJjywkVIHLjcwkFD9Ms0JpxjEm9Mo8eQYkh1z+55dwv4yc1jQ8ftVBxWQbihvZL1DfzGGWA==" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.1.0.tgz", + "integrity": "sha512-PKkwVlAxYVo98NrbclaQIT4F5Oy+X58PZM5r2IwUSCe3syya6PXkIRCn2XCdz7p58Scgpp50PBeHmepXVDG3hg==" }, "makeerror": { "version": "1.0.11", @@ -32993,66 +35169,33 @@ } }, "math-expression-evaluator": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.8.tgz", - "integrity": "sha512-9FbRY3i6U+CbHgrdNbAUaisjWTozkm1ZfupYQJiZ87NtYHk2Zh9DvxMgp/fifxVhqTLpd5fCCLossUbpZxGeKw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.4.0.tgz", + "integrity": "sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==" + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "memfs": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "fs-monkey": "1.0.3" } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -33066,24 +35209,19 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, - "messageformat-parser": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", - "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==" - }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "^3.0.3", + "picomatch": "^2.3.1" } }, "mime": { @@ -33092,16 +35230,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -33110,16 +35248,10 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.1.tgz", + "integrity": "sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g==", "dev": true, "requires": { "dom-walk": "^0.1.0" @@ -33131,15 +35263,6 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "requires": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - } - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -33147,18 +35270,25 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true }, "mixin-deep": { "version": "1.3.2", @@ -33181,11 +35311,10 @@ } } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" }, "mq-polyfill": { "version": "1.1.8", @@ -33199,26 +35328,19 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, "requires": { - "dns-packet": "^1.3.1", + "dns-packet": "^5.2.2", "thunky": "^1.0.2" } }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" }, "nanomatch": { "version": "1.2.13", @@ -33246,9 +35368,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "neo-async": { "version": "2.6.2", @@ -33280,26 +35402,47 @@ } } }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, - "node-gettext": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", - "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", - "dev": true, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { - "lodash.get": "^4.4.2" + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } } }, + "node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "dev": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -33328,9 +35471,9 @@ }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "optional": true, "requires": { @@ -33340,28 +35483,45 @@ } }, "node-releases": { - "version": "1.1.70", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", - "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==" + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "devOptional": true }, "nodemon": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", - "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", "dev": true, "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", + "chokidar": "^3.5.2", + "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" + "undefsafe": "^2.0.5" }, "dependencies": { + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, "debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -33406,12 +35566,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -33430,12 +35584,12 @@ } }, "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "dev": true, "requires": { - "boolbase": "~1.0.0" + "boolbase": "^1.0.0" } }, "nwsapi": { @@ -33444,12 +35598,6 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -33487,20 +35635,9 @@ } }, "object-inspect": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", - "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==", - "dev": true - }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "object-keys": { "version": "1.1.1", @@ -33508,12 +35645,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-path": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.5.tgz", - "integrity": "sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==", - "dev": true - }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -33536,37 +35667,35 @@ } }, "object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" + "es-abstract": "^1.19.1" } }, "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "es-abstract": "^1.19.1" } }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "object.pick": { @@ -33579,17 +35708,22 @@ } }, "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "es-abstract": "^1.19.1" } }, + "observable-fns": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.6.1.tgz", + "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==", + "dev": true + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -33597,18 +35731,17 @@ "dev": true }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } }, "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==" }, "once": { "version": "1.4.0", @@ -33620,35 +35753,29 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", "dev": true, "requires": { - "is-wsl": "^1.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - } + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" } }, "optimism": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.15.0.tgz", - "integrity": "sha512-KLKl3Kb7hH++s9ewRcBhmfpXgXF0xQ+JZ3xQFuPjnoT6ib2TDmYyVkKENmGxivsN2G3VRxpXuauCkB4GYOhtPw==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.1.tgz", + "integrity": "sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==", "requires": { "@wry/context": "^0.6.0", "@wry/trie": "^0.3.0" @@ -33735,27 +35862,6 @@ } } }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "requires": { - "url-parse": "^1.4.3" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, "p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -33777,15 +35883,6 @@ "p-try": "^2.0.0" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", @@ -33793,12 +35890,13 @@ "dev": true }, "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "dev": true, "requires": { - "retry": "^0.12.0" + "@types/retry": "0.12.0", + "retry": "^0.13.1" } }, "p-try": { @@ -33807,42 +35905,30 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" + "dot-case": "^3.0.4", + "tslib": "^2.0.3" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true } } }, - "papaparse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.0.tgz", - "integrity": "sha512-Lb7jN/4bTpiuGPrYy4tkKoUS8sTki8zacB5ke1p5zolhcSE4TlWgrlsxjrDTbG/dFVh07ck7X36hUf/b5V68pg==", - "dev": true - }, - "param-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", - "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", - "dev": true, - "requires": { - "dot-case": "^3.0.3", - "tslib": "^1.10.0" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -33852,13 +35938,13 @@ } }, "parse-json": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", - "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -33897,12 +35983,6 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -33928,30 +36008,47 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "dependencies": { + "lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true + } + } }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true }, "pify": { @@ -33984,34 +36081,10 @@ "node-modules-regexp": "^1.0.0" } }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "plurals-cldr": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/plurals-cldr/-/plurals-cldr-1.0.4.tgz", - "integrity": "sha512-4nLXqtel7fsCgzi8dvRZvUjfL8SXpP982sKg7b2TgpnR8rDnes06iuQ83trQ/+XdtyMIQkBBbKzX6x97eLfsJQ==", - "dev": true - }, "pofile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.0.tgz", - "integrity": "sha512-6XYcNkXWGiJ2CVXogTP7uJ6ZXQCldYLZc16wgRp8tqRaBTTyIfF+TUT3EQJPXTLAT7OTPpTAoaFdoXKfaTRU1w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz", + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==", "dev": true }, "popmotion": { @@ -34032,42 +36105,10 @@ } } }, - "portfinder": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.27.tgz", - "integrity": "sha512-bJ3U3MThKnyJ9Dx1Idtm5pQmxXqw08+XOHhi/Lie8OF1OlhVaBFhsntAIhkZYjfDcCzszSr0w1yCbccThhzgxQ==", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "posix-character-classes": { "version": "0.1.1", @@ -34075,32 +36116,32 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "dev": true }, "pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, "requires": { "lodash": "^4.17.20", - "renderkid": "^2.0.4" + "renderkid": "^3.0.0" } }, "pretty-format": { @@ -34176,13 +36217,13 @@ } }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "property-expr": { @@ -34191,27 +36232,21 @@ "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, "pseudolocale": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.2.0.tgz", - "integrity": "sha512-k0OQFvIlvpRdzR0dPVrrbWX7eE9EaZ6gpZtTlFSDi1Gf9tMy9wiANCNu7JZ0drcKgUri/39a2mBbH0goiQmrmQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-2.1.0.tgz", + "integrity": "sha512-af5fsrRvVwD+MBasBJvuDChT0KDqT0nEwD9NTgbtHJ16FKomWac9ua0z6YVNB4G9x9IOaiGWym62aby6n4tFMA==", "dev": true, "requires": { - "commander": "*" + "commander": "^10.0.0" } }, "psl": { @@ -34242,127 +36277,100 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, + "qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "requires": { - "escape-goat": "^2.0.0" + "side-channel": "^1.1.0" } }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, - "ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "dependencies": { - "strip-json-comments": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "loose-envify": "^1.1.0" } }, "react-clientside-effect": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz", - "integrity": "sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", "requires": { "@babel/runtime": "^7.12.13" } }, "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "dependencies": { - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } + "scheduler": "^0.23.0" } }, "react-error-boundary": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.3.tgz", - "integrity": "sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", "requires": { "@babel/runtime": "^7.12.5" } @@ -34372,17 +36380,55 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "requires": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "dependencies": { + "@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" + }, + "tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "requires": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + } + } + }, "react-focus-lock": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.0.tgz", - "integrity": "sha512-XLxj6uTXgz0US8TmqNU2jMfnXwZG0mH2r/afQqvPEaX6nyEll5LHVcEXk2XDUQ34RVeLPkO/xK5x6c/qiuSq/A==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.12.1.tgz", + "integrity": "sha512-lfp8Dve4yJagkHiFrC1bGtib3mF2ktqwPJw4/WGcgPW+pJ/AVQA5X2vI7xgp13FcxFEpYBBHpXai/N2DBNC0Jw==", "requires": { "@babel/runtime": "^7.0.0", - "focus-lock": "^0.8.1", + "focus-lock": "^1.3.5", "prop-types": "^15.6.2", - "react-clientside-effect": "^1.2.2", - "use-callback-ref": "^1.2.1", - "use-sidecar": "^1.0.1" + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" } }, "react-hot-loader": { @@ -34402,18 +36448,18 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" } }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -34429,19 +36475,47 @@ } } }, - "react-input-autosize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", - "integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==", - "requires": { - "prop-types": "^15.5.8" - } + "react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "requires": {} }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-joyride": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.8.2.tgz", + "integrity": "sha512-2QY8HB1G0I2OT0PKMUz7gg2HAjdkG2Bqi13r0Bb1V16PAwfb9khn4wWBTOJsGsjulbAWiQ3/0YrgNUHGFmuifw==", + "requires": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.18.2" + }, + "dependencies": { + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "type-fest": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", + "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==" + } + } + }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -34449,9 +36523,9 @@ "dev": true }, "react-phone-input-2": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", - "integrity": "sha512-gOY3jUpwO7ulryXPEdqzH7L6DPqI9RQxKfBxZbgqAwXyALGsmwLWFyi2RQwXlBLWN/EPPT4Nv6I9TESVY2YBcg==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz", + "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==", "requires": { "classnames": "^2.2.6", "lodash.debounce": "^4.0.8", @@ -34462,265 +36536,481 @@ } }, "react-remove-scroll": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz", - "integrity": "sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz", + "integrity": "sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==", "requires": { - "react-remove-scroll-bar": "^2.1.0", - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0", - "use-callback-ref": "^1.2.3", - "use-sidecar": "^1.0.1" + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } } }, "react-remove-scroll-bar": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz", - "integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", "requires": { - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0" - } - }, - "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" }, "dependencies": { - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, + "react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "requires": { + "@remix-run/router": "1.23.2" + } + }, "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" } }, - "react-select": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz", - "integrity": "sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==", + "react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, "requires": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.1.1", - "memoize-one": "^5.0.0", - "prop-types": "^15.6.0", - "react-input-autosize": "^3.0.0", - "react-transition-group": "^4.3.0" - }, - "dependencies": { - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - } + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" } }, "react-style-singleton": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz", - "integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", "requires": { "get-nonce": "^1.0.0", "invariant": "^2.2.4", - "tslib": "^1.0.0" - } - }, - "react-swipe": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/react-swipe/-/react-swipe-6.0.4.tgz", - "integrity": "sha512-NIF+gVOqPpE8GyCg0ssFC+fPgeqCwNnvqFU/A8nDAOvoncW3KjSVFwgkYNnErHvpFZGmsVw4SLWK96n7+mnChg==", - "requires": { - "lodash.isequal": "^4.5.0", - "prop-types": "^15.6.0", - "swipe-js-iso": "^2.1.5" + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } } }, "react-table": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.0.tgz", - "integrity": "sha512-16kRTypBWz9ZwLnPWA8hc3eIC64POzO9GaMBiKaCcVM+0QOQzt0G7ebzGUM8SW0CYUpVM+glv1kMXrWj9tr3Sw==" + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", + "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "requires": {} }, "react-test-renderer": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", - "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", + "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", "dev": true, "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" }, "dependencies": { - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true } } }, "react-use-measure": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.4.tgz", - "integrity": "sha512-7K2HIGaPMl3Q9ZQiEVjen3tRXl4UDda8LiTPy/QxP8dP2rl5gPBhf7mMH6MVjjRNv3loU7sNzey/ycPNnHVTxQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", "requires": { - "debounce": "^1.2.0" + "debounce": "^1.2.1" } }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - }, - "dependencies": { - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", + "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "resolve": "^1.9.0" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==", + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" }, "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==" } } }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "regenerate": "^1.4.2" } }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + } + }, + "regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "jsesc": "~0.5.0" }, "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", @@ -34793,12 +37083,6 @@ } } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -34820,28 +37104,13 @@ "to-regex": "^3.0.2" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "remove-trailing-separator": "^1.0.1" } }, "to-regex-range": { @@ -34856,3240 +37125,2068 @@ } } }, - "rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", "dev": true, "requires": { - "resolve": "^1.9.0" + "xmlchars": "^2.2.0" } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "loose-envify": "^1.1.0" } }, - "reduce-function-call": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", - "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, "requires": { - "balanced-match": "^1.0.0" + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" } }, - "regenerate": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", - "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", - "dev": true + "scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } + "scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "@types/node-forge": "^1.3.0", + "node-forge": "^1" } }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, - "regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { - "rc": "^1.2.8" + "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" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "rc": "^1.2.8" + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } } }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "jsesc": "~0.5.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" } } }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "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" + } }, - "renderkid": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", - "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { - "css-select": "^1.1.0", - "dom-converter": "^0.2", - "htmlparser2": "^3.3.0", - "lodash": "^4.17.20", - "strip-ansi": "^3.0.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "is-extendable": "^0.1.0" } } } }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true } } }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "requires": { - "lodash": "^4.17.19" + "kind-of": "^6.0.2" } }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } + "shebang-regex": "^3.0.0" } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true }, - "requires-port": { + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "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" + } + }, + "side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } }, - "resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } }, - "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "requires": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" + "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" } }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", "dev": true, "requires": { - "resolve-from": "^5.0.0" + "semver": "~7.0.0" }, "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true } } }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true }, - "resolve-pathname": { + "slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "lowercase-keys": "^1.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } } }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + } } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "glob": "^7.1.3" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } } }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true - }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, - "rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "tslib": "^1.9.0" + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "requires": { - "ret": "~0.1.10" + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", "dev": true, "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" }, "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" } } } }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { - "xmlchars": "^2.2.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "scheduler": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", - "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, - "selfsigned": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", - "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { - "node-forge": "^0.10.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "requires": { - "semver": "^6.3.0" + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "extend-shallow": "^3.0.0" } }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", "dev": true, "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "escape-string-regexp": "^2.0.0" }, "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true } } }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "safe-buffer": "~5.2.0" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "kind-of": "^6.0.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "dev": true + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "optional": true + "requires": { + "ansi-regex": "^5.0.1" + } }, - "side-channel": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", - "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "es-abstract": "^1.18.0-next.0", - "object-inspect": "^1.8.0" + "ansi-regex": "^5.0.1" } }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "style-value-types": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz", + "integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==", + "requires": { + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "superagent": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.0.tgz", + "integrity": "sha512-iudipXEel+SzlP9y29UBWGDjB+Zzag+eeA1iLosaR2YHBRr1Q1kC29iBrF2zIVD9fqVbpZnXkN/VJmwFMVyNWg==", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "4.0.4", + "formidable": "^2.0.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.10.3", + "readable-stream": "^3.6.0", + "semver": "^7.3.7" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "ms": "2.1.2" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "lru-cache": "^6.0.0" } } } }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "supertest": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.4.tgz", + "integrity": "sha512-M8xVnCNv+q2T2WXVzxDECvL2695Uv2uUj2O0utxsld/HRyJvOU8W9f1gvsYxSNU4wmIe0/L/ItnpU4iKq0emDA==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } + "methods": "^1.1.2", + "superagent": "^8.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "has-flag": "^4.0.0" } } } }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==" + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "table": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz", + "integrity": "sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, - "sockjs": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", - "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", + "tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "requires": { - "faye-websocket": "^0.10.0", - "uuid": "^3.4.0", - "websocket-driver": "0.6.5" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" }, "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true } } }, - "sockjs-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", - "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", + "terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "requires": { - "debug": "^3.2.5", - "eventsource": "^1.0.7", - "faye-websocket": "~0.11.1", - "inherits": "^2.0.3", - "json3": "^3.3.2", - "url-parse": "^1.4.3" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { - "ms": "^2.1.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "websocket-driver": ">=0.5.1" + "fast-deep-equal": "^3.1.3" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true + }, + "schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } } } }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } }, - "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "source-map-loader": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-2.0.1.tgz", - "integrity": "sha512-UzOTTQhoNPeTNzOxwFw220RSRzdGSyH4lpNyWjR7Qm34P4/N0W669YSUFdH07+YNeN75h765XLHmNsF/bm97RQ==", + "threads": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/threads/-/threads-1.7.0.tgz", + "integrity": "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==", "dev": true, "requires": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.2", - "source-map-js": "^0.6.2" + "callsites": "^3.1.0", + "debug": "^4.2.0", + "is-observable": "^2.1.0", + "observable-fns": "^0.6.1", + "tiny-worker": ">= 2" }, "dependencies": { - "iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "ms": "^2.1.3" } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true } } }, - "source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "tiny-worker": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tiny-worker/-/tiny-worker-2.3.0.tgz", + "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==", "dev": true, + "optional": true, "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" + "esm": "^3.2.25" } }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true } } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" + "kind-of": "^3.0.2" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "ms": "^2.1.1" + "is-buffer": "^1.1.5" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "is-number": "^7.0.0" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "dev": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "nopt": "~1.0.10" } }, - "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "requires": { - "escape-string-regexp": "^2.0.0" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true } } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "punycode": "^2.1.1" + } + }, + "tree-changes": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.2.tgz", + "integrity": "sha512-4gXlUthrl+RabZw6lLvcCDl6KfJOCmrC16BC5CRdut1EAH509Omgg0BfKLY+ViRlzrvYOTWR0FMS2SQTwzumrw==", + "requires": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.0" + } + }, + "ts-invariant": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", + "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", + "requires": { + "tslib": "^2.1.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" } } }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "requires": { - "safe-buffer": "~5.2.0" + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" }, "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } } } }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" + "tslib": "^1.8.1" } }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "prelude-ls": "^1.2.1" } }, - "string.prototype.matchall": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", - "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "has-symbols": "^1.0.1", - "internal-slot": "^1.0.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" } }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "is-typedarray": "^1.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "peer": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" }, - "strip-final-newline": { + "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", "dev": true }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "requires": { - "min-indent": "^1.0.0" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" } }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", "dev": true }, - "style-value-types": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz", - "integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==", - "requires": { - "hey-listen": "^1.0.8", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } - } - }, - "stylis": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz", - "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==" + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true }, - "subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, "requires": { - "backo2": "^1.0.2", - "eventemitter3": "^3.1.0", - "iterall": "^1.2.1", - "symbol-observable": "^1.0.4", - "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" - }, - "dependencies": { - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "requires": { - "async-limiter": "~1.0.0" - } - } + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" } }, - "superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } } }, - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true - }, - "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } } } }, - "supertest": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.3.tgz", - "integrity": "sha512-v2NVRyP73XDewKb65adz+yug1XMtmvij63qIWHZzSX8tp6wiq6xBLUy4SUAd2NII6wIipOmHT/FD9eicpJwdgQ==", - "dev": true, + "update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "devOptional": true, "requires": { - "methods": "^1.1.2", - "superagent": "^6.1.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { - "has-flag": "^3.0.0" + "punycode": "^2.1.0" } }, - "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, - "swipe-js-iso": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/swipe-js-iso/-/swipe-js-iso-2.1.5.tgz", - "integrity": "sha512-yTTU5tDYEvtKfCD8PN+Rva25acJwogUCd6wPT1n1im/MOJlg6PtHiPKaaNK7HDBiIrWThA/WRbJZbox2letghg==" - }, - "symbol-observable": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", - "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==" - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, - "synchronous-promise": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.13.tgz", - "integrity": "sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==" + "use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, + "use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, - "tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, - "terminal-link": { + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "v8-compile-cache": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "v8-to-istanbul": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.1.tgz", + "integrity": "sha512-p0BB09E5FRjx0ELN6RgusIPsSPhtgexSRcKETybEs6IGOTXJSZqfwxp7r//55nnu0f1AxltY5VvdVqy2vZf9AA==", "dev": true, "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" }, "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true } } }, - "terser-webpack-plugin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.0.tgz", - "integrity": "sha512-rf7l5a9xamIVX3enQeTl0MY2MNeZClo5yPX/tVPy22oY0nzu0b45h7JqyFi/bygqKWtzXMnml0u12mArhQPsBQ==", - "dev": true, - "requires": { - "jest-worker": "^26.5.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.3.5" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "terser": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", - "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "value-or-promise": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", + "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", "dev": true }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "browser-process-hrtime": "^1.0.0" } }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { + "w3c-xmlserializer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "dependencies": { - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - } - } - }, - "tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-invariant": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.7.3.tgz", - "integrity": "sha512-UWDDeovyUTIMWj+45g5nhnl+8oo+GhxL5leTaHn5c8FkQWfh8v66gccLd2/YzVmV5hoQUjCEjhrXnQqVDJdvKA==", - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - } - } - }, - "ts-is-defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ts-is-defined/-/ts-is-defined-1.0.0.tgz", - "integrity": "sha512-HmzqN8xWETXnfpXyUqMf5nvcZszn9aTNjxVIJ6R2aNNg14oLo3PCi9IRhsv+vg2C7TI90M7PyjBOrg4f6/nupA==", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", "dev": true, "requires": { - "ts-tiny-invariant": "0.0.3" + "xml-name-validator": "^3.0.0" } }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", "dev": true, "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" + "makeerror": "1.0.x" } }, - "ts-tiny-invariant": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/ts-tiny-invariant/-/ts-tiny-invariant-0.0.3.tgz", - "integrity": "sha512-EiaBUsUta7PPzVKpvZurcSDgaSkymxwiUc2rhX6Wu30bws2maipT6ihbEY072dU9lz6/FoFWEc6psXdlo0xqtg==", - "dev": true - }, - "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" } }, - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" - }, - "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "requires": { - "tslib": "^1.8.1" + "minimalistic-assert": "^1.0.0" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "defaults": "^1.0.3" } }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, - "peer": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "requires": { - "debug": "^2.2.0" - } - }, - "unfetch": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, - "update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dev": true, - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "use-callback-ref": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", - "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==", - "requires": {} - }, - "use-sidecar": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz", - "integrity": "sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==", - "requires": { - "detect-node-es": "^1.1.0", - "tslib": "^1.9.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", - "dev": true - }, - "v8-to-istanbul": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.1.tgz", - "integrity": "sha512-p0BB09E5FRjx0ELN6RgusIPsSPhtgexSRcKETybEs6IGOTXJSZqfwxp7r//55nnu0f1AxltY5VvdVqy2vZf9AA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "value-or-promise": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.6.tgz", - "integrity": "sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "watchpack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.0.0.tgz", - "integrity": "sha512-xSdCxxYZWNk3VK13bZRYhsQpfa8Vg63zXG+3pyU8ouqSLRCv4IGXIp9Kr226q6GBkGRlZrST2wwKtjfKz2m7Cg==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "webpack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.2.0.tgz", - "integrity": "sha512-evtOjOJQq3zaHJIWsJjM4TGtNHtSrNVAIyQ+tdPW/fRd+4PLGbUG6S3xt+N4+QwDBOaCVd0xCWiHd4R6lWO5DQ==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.45", - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^8.0.4", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.3.0", - "eslint-scope": "^5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.1.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "pkg-dir": "^4.2.0", - "schema-utils": "^3.0.0", - "tapable": "^2.0.0", - "terser-webpack-plugin": "^5.0.0", - "watchpack": "^2.0.0", - "webpack-sources": "^2.0.1" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", - "dev": true - }, - "webpack-sources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.0.1.tgz", - "integrity": "sha512-A9oYz7ANQBK5EN19rUXbvNgfdfZf5U2gP0769OXsj9CvYkCR6OHOsd6OKyEy4H38GGxpsQPKIL83NC64QY6Xmw==", - "dev": true, - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - } - } - } - }, - "webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", - "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "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" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - } - } - }, - "webpack-config-utils": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-config-utils/-/webpack-config-utils-2.3.1.tgz", - "integrity": "sha512-0uC5uj7sThFTePTQjfpe5Wqcbw3KSCxqswOmW96lwk2ZI2CU098rWY2ZqOVGJQYJ3hfEltmjcLNkKutw8LJAlg==", - "dev": true, - "requires": { - "webpack-combine-loaders": "2.0.4" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", - "dev": true - }, - "webpack-combine-loaders": { - "version": "2.0.4", - "integrity": "sha1-J4FNUrgyntZWW+OQCarHY2Hn4iw=", - "dev": true, - "requires": { - "qs": "^6.5.2" - } - } - } - }, - "webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", - "dev": true, - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "webpack-dev-server": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", - "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.7", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "0.3.20", - "sockjs-client": "1.4.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "webpack": { + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "dev": true, + "requires": { + "@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.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "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.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "fast-deep-equal": "^3.1.3" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + } + } + }, + "webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, + "webpack-config-utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-config-utils/-/webpack-config-utils-2.3.1.tgz", + "integrity": "sha512-0uC5uj7sThFTePTQjfpe5Wqcbw3KSCxqswOmW96lwk2ZI2CU098rWY2ZqOVGJQYJ3hfEltmjcLNkKutw8LJAlg==", + "dev": true, + "requires": { + "webpack-combine-loaders": "2.0.4" + }, + "dependencies": { + "webpack-combine-loaders": { + "version": "2.0.4", + "integrity": "sha1-J4FNUrgyntZWW+OQCarHY2Hn4iw=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "qs": "^6.5.2" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { - "resolve-from": "^3.0.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "fast-deep-equal": "^3.1.3" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + } + } + }, + "webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "fast-deep-equal": "^3.1.3" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" } }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.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": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "glob": "^7.1.3" } }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" } - } - } - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "requires": {} } } }, @@ -38103,12 +39200,20 @@ "wildcard": "^2.0.0" } }, + "webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true + }, "websocket-driver": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", - "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, @@ -38172,13 +39277,19 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "requires": { - "string-width": "^4.0.0" + "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" } }, "wildcard": { @@ -38188,9 +39299,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrap-ansi": { @@ -38230,32 +39341,49 @@ } } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "minimist": "^1.2.5" + "color-name": "~1.1.4" } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true } } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -38269,16 +39397,11 @@ } }, "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", - "dev": true - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", @@ -38293,9 +39416,9 @@ "dev": true }, "xss": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz", - "integrity": "sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", + "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", "dev": true, "requires": { "commander": "^2.20.3", @@ -38323,9 +39446,9 @@ "dev": true }, "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==" }, "yargs": { "version": "15.4.1", @@ -38400,23 +39523,17 @@ } } }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, "yup": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.29.3.tgz", - "integrity": "sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==", - "requires": { - "@babel/runtime": "^7.10.5", - "fn-name": "~3.0.0", - "lodash": "^4.17.15", - "lodash-es": "^4.17.11", - "property-expr": "^2.0.2", - "synchronous-promise": "^2.0.13", + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", "toposort": "^2.0.2" } }, @@ -38424,16 +39541,6 @@ "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" - }, - "zen-observable-ts": { - "version": "0.8.21", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz", - "integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==", - "dev": true, - "requires": { - "tslib": "^1.9.3", - "zen-observable": "^0.8.0" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 0ebf247b09..f25a87c314 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,10 +3,13 @@ "version": "0.1.0", "scripts": { "build": "webpack --env production --config webpack.config.js", + "build:dev": "webpack --env development --config webpack.config.js", "dev": "webpack serve --hot --env development", "webpack": "webpack", - "start": "node index.js", - "test": "jest", + "start": "NODE_OPTIONS=--openssl-legacy-provider node index.js", + "start:dev": "NODE_ENV=development PORT=3300 HOST=127.0.0.1 node index.js", + "test": "jest --silent", + "test-coverage": "jest --coverage", "lint": "eslint src", "dbg": "node --inspect-brk node_modules/.bin/jest --runInBand --no-cache", "extract": "lingui extract", @@ -14,97 +17,104 @@ "mocker": "nodemon --watch mocking/ --exec 'babel mocking/ -d ./dist/mocking && node dist/mocking/mocker.js'" }, "dependencies": { - "@apollo/client": "^3.3.13", - "@babel/runtime": "^7.12.1", - "@chakra-ui/icons": "^1.0.14", - "@chakra-ui/react": "^1.6.5", - "@emotion/react": "^11.4.0", - "@emotion/styled": "^11.3.0", - "@lingui/react": "^3.10.2", - "@visx/axis": "^1.17.0", - "@visx/event": "^1.7.0", - "@visx/grid": "^1.16.0", - "@visx/group": "^1.7.0", - "@visx/legend": "^1.14.0", - "@visx/mock-data": "^1.7.0", - "@visx/responsive": "^1.10.1", - "@visx/scale": "^1.14.0", - "@visx/shape": "^1.16.0", - "@visx/tooltip": "^1.7.2", - "body-parser": "^1.19.0", - "d3": "^6.2.0", - "d3-scale": "^3.2.3", - "d3-selection": "^2.0.0", - "dotenv": "^8.2.0", - "express": "^4.17.1", - "fast-deep-equal": "^3.1.3", - "formik": "^2.2.0", + "@apollo/client": "^3.6.9", + "@babel/runtime": "^7.27.0", + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@chakra-ui/system": "^2.6.2", + "@emotion/react": "^11.9.3", + "@emotion/styled": "^11.9.3", + "@lingui/react": "^5.9.3", + "@visx/axis": "^2.17.0", + "@visx/curve": "^3.3.0", + "@visx/event": "^2.17.0", + "@visx/glyph": "^3.3.0", + "@visx/gradient": "^3.3.0", + "@visx/grid": "^3.5.0", + "@visx/group": "^2.1.0", + "@visx/legend": "^2.17.0", + "@visx/mock-data": "^3.3.0", + "@visx/responsive": "^2.17.0", + "@visx/scale": "^3.5.0", + "@visx/shape": "^3.5.0", + "@visx/tooltip": "^2.17.0", + "@visx/vendor": "^3.5.0", + "body-parser": "^1.20.4", + "compression": "^1.8.1", + "d3": "^7.8.0", + "dotenv": "^16.0.1", + "express": "^4.22.0", + "formik": "^2.2.9", "framer-motion": "^4.1.17", - "graphql-tag": "^2.11.0", + "intro.js": "^7.2.0", + "intro.js-react": "^1.0.0", "isomorphic-unfetch": "^3.1.0", - "make-plural": "^6.2.2", - "prop-types": "^15.7.2", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-error-boundary": "^3.1.3", - "react-phone-input-2": "^2.14.0", - "react-router-dom": "^5.2.0", - "react-select": "^4.3.1", - "react-swipe": "^6.0.4", - "react-table": "^7.6.0", - "subscriptions-transport-ws": "^0.9.19", - "yup": "^0.29.3" + "json-2-csv": "^3.17.1", + "lodash-es": "^4.18.1", + "make-plural": "^7.1.0", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.4", + "react-joyride": "^2.8.2", + "react-phone-input-2": "^2.15.1", + "react-router-dom": "^6.30.3", + "react-table": "^7.8.0", + "yup": "^0.32.11" }, "devDependencies": { - "@babel/cli": "^7.13.16", - "@babel/core": "^7.12.10", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/preset-env": "^7.12.11", - "@babel/preset-react": "^7.13.13", - "@graphql-tools/mock": "^8.1.3", - "@graphql-tools/schema": "^7.1.5", - "@hot-loader/react-dom": "^16.14.0", - "@lingui/cli": "^3.10.2", - "@lingui/loader": "^3.10.2", - "@lingui/macro": "^3.10.2", - "@testing-library/jest-dom": "^5.11.5", - "@testing-library/react": "^11.1.1", - "@testing-library/react-hooks": "^5.0.0", - "@testing-library/user-event": "^13.1.9", - "acorn": "^8.1.0", - "apollo-server": "^2.24.0", - "babel-loader": "^8.2.2", - "babel-plugin-macros": "^3.0.1", + "@babel/cli": "^7.18.6", + "@babel/core": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-transform-runtime": "^7.18.6", + "@babel/preset-env": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@graphql-tools/mock": "^8.7.0", + "@graphql-tools/schema": "^10.0.0", + "@lingui/cli": "^5.9.3", + "@lingui/core": "^5.9.3", + "@lingui/loader": "^5.9.3", + "@lingui/macro": "^5.9.3", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.4.0", + "@testing-library/react-hooks": "^7.0.2", + "@testing-library/user-event": "^13.5.0", + "acorn": "^8.7.0", + "apollo-server": "^3.12.0", + "babel-loader": "^8.2.5", + "babel-plugin-macros": "^3.1.0", "clean-webpack-plugin": "^3.0.0", - "eslint": "^7.11.0", - "eslint-config-prettier": "^8.1.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.3.1", - "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-standard": "^4.0.1", - "faker": "^5.5.3", - "graphql": "^15.5.0", - "graphql-relay": "^0.6.0", - "html-webpack-plugin": "^5.0.0-beta.6", - "jest": "^26.6.2", - "jest-emotion": "^10.0.32", - "jsonwebtoken": "^8.5.1", + "copy-webpack-plugin": "^14.0.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "faker": "5.5.3", + "graphql": "^16.8.1", + "graphql-relay": "^0.10.0", + "graphql-subscriptions": "^2.0.0", + "html-webpack-plugin": "^5.5.0", + "jest": "^26.6.3", + "jest-emotion": "^11.0.0", + "jsonwebtoken": "^9.0.0", "mq-polyfill": "^1.1.8", - "nodemon": "^2.0.7", - "prettier": "^2.1.2", + "nodemon": "^2.0.19", + "prettier": "^2.7.1", "react-hot-loader": "^4.13.0", - "react-test-renderer": "^16.14.0", - "source-map-loader": "^2.0.1", - "supertest": "^6.1.3", - "webpack": "^5.0.2", - "webpack-cli": "^4.6.0", + "react-test-renderer": "^18.2.0", + "source-map-loader": "^4.0.0", + "supertest": "^6.2.3", + "webpack": "^5.105.0", + "webpack-cli": "^4.10.0", "webpack-config-utils": "^2.3.1", - "webpack-dev-server": "^3.11.0" + "webpack-dev-server": "^4.9.2" + }, + "overrides": { + "colors": "1.4.0", + "form-data@^3.0.0": "3.0.4", + "form-data@^4.0.0": "4.0.4" }, "repository": { "type": "git", @@ -117,7 +127,10 @@ "jest": { "verbose": true, "moduleNameMapper": { - "\\.(jpg|jpeg|png|gif|svg)$": "/__mocks__/fileMock.js" + "\\.(jpg|jpeg|png|gif|svg)$": "/__mocks__/fileMock.js", + "^d3$": "/node_modules/d3/dist/d3.min.js", + "^d3-[\\w]+$": "/node_modules/d3/dist/d3.min.js", + "^lodash-es$": "lodash" }, "setupFilesAfterEnv": [ "./src/setupTests.js" diff --git a/frontend/src/Server.js b/frontend/src/Server.js index 702a2a6c01..526c3edb3a 100644 --- a/frontend/src/Server.js +++ b/frontend/src/Server.js @@ -1,12 +1,35 @@ const { join, resolve } = require('path') const express = require('express') +const compression = require('compression') const bodyParser = require('body-parser') +const fs = require('fs') const staticPath = join(resolve(process.cwd()), 'public') +const frenchHosts = process.env.FRENCH_HOSTS?.split(',') || [] +const isProduction = process.env.TRACKER_PRODUCTION === 'true' + +const baseHtml = fs.readFileSync(resolve(join('public', 'index.html')), 'utf8') + +const htmlByLanguage = { + en: baseHtml.replace( + '', + ``, + ), + fr: baseHtml.replace( + '', + ``, + ), +} + +function isHashed(filePath) { + return /\.[0-9a-f]{8,}\./i.test(filePath) +} + function Server() { const server = express() server.use(bodyParser.json()) + server.use(compression()) server.disable('x-powered-by') server.set('trust proxy', true) @@ -18,11 +41,37 @@ function Server() { res.json({ status: 'ready' }) }) - server.use('/', express.static(staticPath, { maxage: '365d' })) + server.use( + ['/manifest.json', '/robots.txt', '/favicon.ico'], + express.static(staticPath, { maxAge: '1d', index: false }), + ) - server.get('*', (_req, res) => { - res.sendFile(resolve(join('public', 'index.html'))) + server.use( + '/', + express.static(staticPath, { + index: false, + setHeaders(res, filePath) { + if (isHashed(filePath)) { + // filePath contains a hash? Cache for 1 year + res.setHeader('Cache-Control', 'public, max-age=31536000') + } else { + // file not already handled and does not contain a hash? Cache for 1d + res.setHeader('Cache-Control', 'public, max-age=86400') + } + }, + }), + ) + + server.get('*', (req, res) => { + const host = req.hostname + const lang = frenchHosts.includes(host) ? 'fr' : 'en' + + res.set('Cache-Control', 'no-cache') + + res.send(htmlByLanguage[lang]) }) + return server } + module.exports.Server = Server diff --git a/frontend/src/admin/AdminDomainCard.js b/frontend/src/admin/AdminDomainCard.js index 47a49fa7b5..ae329088d1 100644 --- a/frontend/src/admin/AdminDomainCard.js +++ b/frontend/src/admin/AdminDomainCard.js @@ -1,37 +1,74 @@ import React from 'react' -import { Trans } from '@lingui/macro' -import { string } from 'prop-types' -import { Grid, Link, ListItem, Stack, Text } from '@chakra-ui/react' -import { ExternalLinkIcon } from '@chakra-ui/icons' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { any, array, bool, object, string } from 'prop-types' +import { Flex, ListItem, Tag, TagLabel, Text, Tooltip } from '@chakra-ui/react' -import { sanitizeUrl } from '../utilities/sanitizeUrl' +export function AdminDomainCard({ url, tags, assetState, isArchived, rcode, cvdEnrollment, children, ...rest }) { + const assetStateLabels = { + APPROVED: t`Approved`, + DEPENDENCY: t`Dependency`, + MONITOR_ONLY: t`Monitor Only`, + CANDIDATE: t`Candidate`, + REQUIRES_INVESTIGATION: t`Requires Investigation`, + } -export function AdminDomainCard({ url, ...rest }) { return ( - - - - Domain: - - - {url} - - + + {children} + + {url} + + + {tags?.map(({ label, description }, idx) => { + return ( + + + + {label.toUpperCase()} + + + + ) + })} + + + {assetState && ( + + {assetStateLabels[assetState]} + + )} + {cvdEnrollment && cvdEnrollment.status !== 'NOT_ENROLLED' && ( + + + CVD {cvdEnrollment.status} + + + )} + {rcode === 'NXDOMAIN' && ( + + NXDOMAIN + + )} + {isArchived && ( + + + ARCHIVED + + + )} + + ) } -AdminDomainCard.propTypes = { url: string } +AdminDomainCard.propTypes = { + url: string, + tags: array, + isArchived: bool, + rcode: string, + assetState: string, + children: any, + cvdEnrollment: object, +} diff --git a/frontend/src/admin/AdminDomainList.js b/frontend/src/admin/AdminDomainList.js new file mode 100644 index 0000000000..834fd1c615 --- /dev/null +++ b/frontend/src/admin/AdminDomainList.js @@ -0,0 +1,92 @@ +import React from 'react' +import { IconButton, Text } from '@chakra-ui/react' +import { ListOf } from '../components/ListOf' +import { Trans } from "@lingui/react/macro" +import { EditIcon, MinusIcon } from '@chakra-ui/icons' +import { AdminDomainCard } from './AdminDomainCard' +import { array, bool, func, object, string } from 'prop-types' + +export function AdminDomainList({ + nodes, + verified, + permission, + i18n, + setSelectedRemoveProps, + removeOnOpen, + setModalProps, + updateOnOpen, +}) { + return ( + ( + + No Domains + + )} + > + {({ id: domainId, domain, claimTags, archived, rcode, organizations, assetState, cvdEnrollment }, index) => ( + + + {(!verified || permission === 'SUPER_ADMIN' || rcode === 'NXDOMAIN') && ( + { + setSelectedRemoveProps({ domain, domainId, rcode }) + removeOnOpen() + }} + variant="danger" + px="2" + icon={} + aria-label={'Remove ' + domain} + mr="1" + /> + )} + { + setModalProps({ + archived, + mutation: 'update', + assetState, + tagInputList: claimTags, + editingDomainId: domainId, + editingDomainUrl: domain, + orgCount: organizations.totalCount, + cvdEnrollment, + permission, + }) + updateOnOpen() + }} + icon={} + aria-label={'Edit ' + domain} + mr="2" + /> + + + )} + + ) +} +AdminDomainList.propTypes = { + nodes: array.isRequired, + verified: bool, + permission: string, + i18n: object.isRequired, + setSelectedRemoveProps: func.isRequired, + removeOnOpen: func.isRequired, + setModalProps: func.isRequired, + updateOnOpen: func.isRequired, +} diff --git a/frontend/src/admin/AdminDomainModal.js b/frontend/src/admin/AdminDomainModal.js index 825f5438c0..190620ac4e 100644 --- a/frontend/src/admin/AdminDomainModal.js +++ b/frontend/src/admin/AdminDomainModal.js @@ -1,14 +1,15 @@ import React, { useRef } from 'react' -import { t, Trans } from '@lingui/macro' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { useLingui } from '@lingui/react' import { + Badge, Box, Button, + Divider, + Flex, FormControl, - FormErrorMessage, - Grid, - IconButton, - Input, + FormLabel, Modal, ModalBody, ModalCloseButton, @@ -16,35 +17,52 @@ import { ModalFooter, ModalHeader, ModalOverlay, + Select, + SimpleGrid, Stack, + Switch, + Tag, + TagCloseButton, + TagLabel, Text, + Tooltip, useToast, } from '@chakra-ui/react' -import { MinusIcon, SmallAddIcon } from '@chakra-ui/icons' -import { array, bool, func, object, string } from 'prop-types' -import { Field, FieldArray, Formik } from 'formik' +import { AddIcon, QuestionOutlineIcon } from '@chakra-ui/icons' +import { array, bool, func, number, object, string } from 'prop-types' +import { FieldArray, Formik } from 'formik' import { useMutation } from '@apollo/client' +import { ABTestVariant, ABTestWrapper } from '../app/ABTestWrapper' import { DomainField } from '../components/fields/DomainField' import { CREATE_DOMAIN, UPDATE_DOMAIN } from '../graphql/mutations' +import withSuperAdmin from '../app/withSuperAdmin' +import { CvdEnrollmentForm } from './CvdEnrollmentForm' export function AdminDomainModal({ isOpen, onClose, validationSchema, orgId, + availableTags, editingDomainId, editingDomainUrl, - selectorInputList, + tagInputList, orgSlug, + archived, + assetState, mutation, + orgCount, + cvdEnrollment, + permission, + ...rest }) { const toast = useToast() const initialFocusRef = useRef() const { i18n } = useLingui() const [createDomain] = useMutation(CREATE_DOMAIN, { - refetchQueries: ['PaginatedOrgDomains'], + refetchQueries: ['PaginatedOrgDomains', 'FindAuditLogs'], onError(error) { toast({ title: i18n._(t`An error occurred.`), @@ -57,17 +75,15 @@ export function AdminDomainModal({ }, onCompleted({ createDomain }) { if (createDomain.result.__typename === 'Domain') { + onClose() toast({ title: i18n._(t`Domain added`), - description: i18n._( - t`${createDomain.result.domain} was added to ${orgSlug}`, - ), + description: i18n._(t`${createDomain.result.domain} was added to ${orgSlug}`), status: 'success', duration: 9000, isClosable: true, position: 'top-left', }) - onClose() } else if (createDomain.result.__typename === 'DomainError') { toast({ title: i18n._(t`Unable to create new domain.`), @@ -92,8 +108,7 @@ export function AdminDomainModal({ }) const [updateDomain] = useMutation(UPDATE_DOMAIN, { - refetchQueries: ['PaginatedOrgDomains'], - + refetchQueries: ['FindAuditLogs'], onError(error) { toast({ title: i18n._(t`An error occurred.`), @@ -106,17 +121,15 @@ export function AdminDomainModal({ }, onCompleted({ updateDomain }) { if (updateDomain.result.__typename === 'Domain') { + onClose() toast({ title: i18n._(t`Domain updated`), - description: i18n._( - t`${editingDomainUrl} from ${orgSlug} successfully updated to ${updateDomain.result.domain}`, - ), + description: i18n._(t`${editingDomainUrl} from ${orgSlug} successfully updated.`), status: 'success', duration: 9000, isClosable: true, position: 'top-left', }) - onClose() } else if (updateDomain.result.__typename === 'DomainError') { toast({ title: i18n._(t`Unable to update domain.`), @@ -138,21 +151,69 @@ export function AdminDomainModal({ console.log('Incorrect updateDomain.result typename.') } }, + update: (cache, { data }) => { + if (data.updateDomain.result.__typename !== 'Domain') return + + const updateDomainId = cache.identify(data.updateDomain.result) + cache.modify({ + id: updateDomainId, + fields: { + claimTags() { + return data.updateDomain.result.claimTags + }, + }, + }) + }, }) + const addableTags = (values, helper) => { + const stringValues = values?.map(({ tagId }) => { + return tagId + }) + const difference = availableTags.filter(({ tagId }) => !stringValues?.includes(tagId)) + return difference?.map((tag, idx) => { + return ( + + ) + }) + } + + const getInitTags = () => { + let tags = tagInputList?.map((label) => { + return availableTags.filter((option) => option.tagId == label.tagId)[0] + }) + if (mutation === 'create' && tags.filter(({ tagId }) => tagId === 'new-nouveau').length === 0) { + const newTag = availableTags.filter(({ tagId }) => tagId === 'new-nouveau')[0] + newTag && tags.push(newTag) + } + return tags + } + return ( - + { // Submit update detail mutation - + const sanitizeCvdEnrollment = (enrollment) => { + if (!enrollment || typeof enrollment !== 'object') return enrollment + const { __typename, ...rest } = enrollment + return rest + } if (mutation === 'update') { await updateDomain({ variables: { domainId: editingDomainId, orgId: orgId, - domain: values.domainUrl, - selectors: values.selectors, + tags: values.tags.map(({ tagId }) => tagId), + archived: values.archiveDomain, + assetState: values.assetState, + ignoreRua: values.ignoreRua, + cvdEnrollment: sanitizeCvdEnrollment(values.cvdEnrollment), }, }) } else if (mutation === 'create') { await createDomain({ variables: { orgId: orgId, - domain: values.domainUrl, - selectors: values.selectors, + domain: values.domainUrl.trim(), + tags: values.tags.map(({ tagId }) => tagId), + archived: values.archiveDomain, + assetState: values.assetState, + cvdEnrollment: sanitizeCvdEnrollment(values.cvdEnrollment), }, }) } }} > - {({ handleSubmit, isSubmitting, values, errors, touched }) => ( + {({ handleSubmit, handleChange, isSubmitting, values }) => (
- {mutation === 'update' ? ( - Edit Domain Details - ) : ( - Add Domain Details - )} + {mutation === 'update' ? Edit Domain Details : Add Domain Details} - - + {mutation === 'create' ? ( + + ) : ( + + Domain: + {editingDomainUrl} + + )} ( - - DKIM Selectors: - - {values.selectors.map((_selector, index) => ( - - - } - data-testid="remove-dkim-selector" - type="button" - p="3" - onClick={() => arrayHelpers.remove(index)} - aria-label="remove-dkim-selector" - /> - - {({ field }) => ( - - )} - - - - {errors && - errors.selectors && - errors.selectors[index]} - - - - ))} - } - data-testid="add-dkim-selector" - type="button" - px="2" - onClick={() => arrayHelpers.push('')} - aria-label="add-dkim-selector" - /> + Tags: + + {values.tags?.map(({ tagId, label, description }, idx) => { + return ( + + + {label.toUpperCase()} + + arrayHelpers.remove(idx)} + aria-label={`remove-tag-${tagId}`} + /> + + ) + })} + + + + {addableTags(values.tags, arrayHelpers)} + )} /> + + + + + Asset State + + + + + + + + + + + + + + + + Please allow up to 24 hours for summaries to reflect any changes. + - @@ -288,6 +362,61 @@ export function AdminDomainModal({ ) } +const ArchiveDomainSwitch = withSuperAdmin(({ defaultChecked, handleChange, orgCount }) => { + return ( + + + + + + + + Archive domain + + + + + {orgCount > 0 ? ( + Note: This will affect results for {orgCount} organizations + ) : ( + Note: This could affect results for multiple organizations + )} + + + ) +}) + +const IgnoreRuaToggle = withSuperAdmin(({ defaultChecked, handleChange }) => { + return ( + + + + + Ignore RUA + + + + ) +}) + AdminDomainModal.propTypes = { isOpen: bool, onClose: func, @@ -295,7 +424,15 @@ AdminDomainModal.propTypes = { orgId: string, editingDomainId: string, editingDomainUrl: string, - selectorInputList: array, + tagInputList: array, + archived: bool, orgSlug: string, mutation: string, + orgCount: number, + refetchQueries: array, + myOrg: object, + assetState: string, + cvdEnrollment: object, + availableTags: array, + permission: string, } diff --git a/frontend/src/admin/AdminDomains.js b/frontend/src/admin/AdminDomains.js index 6fe425cc9d..a9ed415664 100644 --- a/frontend/src/admin/AdminDomains.js +++ b/frontend/src/admin/AdminDomains.js @@ -1,11 +1,13 @@ -import React, { useCallback, useState } from 'react' -import { t, Trans } from '@lingui/macro' +import React, { useCallback, useEffect, useState } from 'react' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { Box, Button, Divider, Flex, - IconButton, + FormControl, + FormLabel, Input, InputGroup, InputLeftElement, @@ -16,52 +18,79 @@ import { ModalFooter, ModalHeader, ModalOverlay, + Select, Stack, Text, useDisclosure, useToast, } from '@chakra-ui/react' -import { AddIcon, EditIcon, MinusIcon, PlusSquareIcon } from '@chakra-ui/icons' +import { AddIcon, EditIcon, HamburgerIcon, PlusSquareIcon } from '@chakra-ui/icons' import { useMutation } from '@apollo/client' import { useLingui } from '@lingui/react' -import { number, string } from 'prop-types' +import { array, bool, number, string } from 'prop-types' import { AdminDomainModal } from './AdminDomainModal' -import { AdminDomainCard } from './AdminDomainCard' -import { ListOf } from '../components/ListOf' import { LoadingMessage } from '../components/LoadingMessage' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { RelayPaginationControls } from '../components/RelayPaginationControls' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' -import { createValidationSchema } from '../utilities/fieldRequirements' +import { createValidationSchema, getRequirement, schemaToValidation } from '../utilities/fieldRequirements' import { usePaginatedCollection } from '../utilities/usePaginatedCollection' import { PAGINATED_ORG_DOMAINS_ADMIN_PAGE as FORWARD } from '../graphql/queries' import { REMOVE_DOMAIN } from '../graphql/mutations' +import { Formik } from 'formik' +import { InfoBox, InfoButton, InfoPanel } from '../components/InfoPanel' +import { FilterList } from '../domains/FilterList' +import { domainSearchTip } from '../domains/DomainsPage' +import useSearchParam from '../utilities/useSearchParam' +import { ABTestVariant, ABTestWrapper } from '../app/ABTestWrapper' +import { DomainUpdateList } from './DomainUpdateList' +import { AdminDomainList } from './AdminDomainList' -export function AdminDomains({ orgSlug, domainsPerPage, orgId }) { +export function AdminDomains({ orgSlug, orgId, verified, permission, availableTags }) { + const [showUpdateList, setShowUpdateList] = useState(false) const toast = useToast() const { i18n } = useLingui() const [newDomainUrl, setNewDomainUrl] = useState('') - const [editingDomainUrl, setEditingDomainUrl] = useState() - const [editingDomainId, setEditingDomainId] = useState() - const [selectedRemoveDomainUrl, setSelectedRemoveDomainUrl] = useState() - const [selectedRemoveDomainId, setSelectedRemoveDomainId] = useState() + const [domainsPerPage, setDomainsPerPage] = useState(50) + const [selectedRemoveProps, setSelectedRemoveProps] = useState({ + domain: '', + domainId: '', + rcode: '', + }) const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('') - const [selectorInputList, setSelectorInputList] = useState([]) - const [mutation, setMutation] = useState() + const [modalProps, setModalProps] = useState({ + archived: false, + mutation: '', + tagInputList: [], + assetState: '', + editingDomainId: '', + editingDomainUrl: '', + cvdEnrollment: { status: 'NOT_ENROLLED' }, + }) + const { searchValue: filters, setSearchParams: setFilters } = useSearchParam({ + name: 'domain-filters', + defaultValue: [], + }) - const { - isOpen: updateIsOpen, - onOpen: updateOnOpen, - onClose: updateOnClose, - } = useDisclosure() - const { - isOpen: removeIsOpen, - onOpen: removeOnOpen, - onClose: removeOnClose, - } = useDisclosure() + const { isOpen: updateIsOpen, onOpen: updateOnOpen, onClose: updateOnClose } = useDisclosure() + const { isOpen: removeIsOpen, onOpen: removeOnOpen, onClose: removeOnClose } = useDisclosure() + const { isOpen: infoIsOpen, onToggle } = useDisclosure() + + const validationSchema = schemaToValidation({ + filterCategory: getRequirement('field'), + comparison: getRequirement('field'), + filterValue: getRequirement('field'), + }) + + const fetchVariables = { + orgSlug, + search: debouncedSearchTerm, + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters, + } const { loading, @@ -70,12 +99,14 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId }) { nodes, next, previous, + resetToFirstPage, hasNextPage, hasPreviousPage, + totalCount, } = usePaginatedCollection({ fetchForward: FORWARD, recordsPerPage: domainsPerPage, - variables: { orgSlug, search: debouncedSearchTerm }, + variables: fetchVariables, relayRoot: 'findOrganizationBySlug.domains', fetchPolicy: 'cache-and-network', nextFetchPolicy: 'cache-first', @@ -87,232 +118,471 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId }) { useDebouncedFunction(memoizedSetDebouncedSearchTermCallback, 500) - const [removeDomain, { loading: removeDomainLoading }] = useMutation( - REMOVE_DOMAIN, - { - refetchQueries: ['PaginatedOrgDomains'], - onError(error) { + useEffect(() => { + resetToFirstPage() + }, [orgSlug]) + + const [removeDomain] = useMutation(REMOVE_DOMAIN, { + refetchQueries: ['FindAuditLogs'], + update(cache, { data: { removeDomain } }) { + if (removeDomain.result.__typename === 'DomainResult') { + cache.evict({ id: cache.identify(removeDomain.result.domain) }) + } + }, + onError(error) { + toast({ + title: i18n._(t`An error occurred.`), + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ removeDomain }) { + if (removeDomain.result.__typename === 'DomainResult') { + removeOnClose() + toast({ + title: i18n._(t`Domain removed`), + description: i18n._(t`Domain removed from ${orgSlug}`), + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else if (removeDomain.result.__typename === 'DomainError') { toast({ - title: i18n._(t`An error occurred.`), - description: error.message, + title: i18n._(t`Unable to remove domain.`), + description: removeDomain.result.description, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ removeDomain }) { - if (removeDomain.result.__typename === 'DomainResult') { - removeOnClose() - toast({ - title: i18n._(t`Domain removed`), - description: i18n._(t`Domain removed from ${orgSlug}`), - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else if (removeDomain.result.__typename === 'DomainError') { - toast({ - title: i18n._(t`Unable to remove domain.`), - description: removeDomain.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else { - toast({ - title: i18n._(t`Incorrect send method received.`), - description: i18n._(t`Incorrect removeDomain.result typename.`), - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - console.log('Incorrect removeDomain.result typename.') - } - }, + } else { + toast({ + title: i18n._(t`Incorrect send method received.`), + description: i18n._(t`Incorrect removeDomain.result typename.`), + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect removeDomain.result typename.') + } }, - ) + }) if (error) return + const filterTagOptions = [ + ...availableTags?.map(({ tagId, label }) => ({ + value: tagId, + text: label.toUpperCase(), + })), + { value: `NXDOMAIN`, text: `NXDOMAIN` }, + { value: `BLOCKED`, text: t`Blocked` }, + { value: `WILDCARD_SIBLING`, text: t`Wildcard Sibling` }, + { value: `WILDCARD_ENTRY`, text: t`Wildcard Entry` }, + { value: `SCAN_PENDING`, text: t`Scan Pending` }, + { value: `ARCHIVED`, text: t`Archived` }, + { value: `CVE_DETECTED`, text: t`SPIN Top 25` }, + { value: 'CVD_ENROLLED', text: t`CVD Enrolled` }, + { value: 'CVD_PENDING', text: t`CVD Pending` }, + { value: 'CVD_DENY', text: t`CVD Denied` }, + ] + const adminDomainList = loading ? ( Domain List ) : ( - ( - - No Domains - - )} - > - {({ id: domainId, domain, selectors }, index) => ( - - - - { - setSelectedRemoveDomainUrl(domain) - setSelectedRemoveDomainId(domainId) - removeOnOpen() - }} - variant="danger" - px="2" - icon={} - aria-label={'Remove ' + domain} - /> - { - setEditingDomainUrl(domain) - setEditingDomainId(domainId) - setSelectorInputList(selectors) - setMutation('update') - updateOnOpen() - }} - icon={} - aria-label={'Edit ' + domain} - /> - - - - - - )} - - ) - - return ( - - { - e.preventDefault() // prevents page from refreshing - setSelectorInputList([]) - setEditingDomainUrl(newDomainUrl) - setMutation('create') - updateOnOpen() + <> + { + setFilters([ + ...new Map( + [...filters, values].map((item) => { + if (item['filterCategory'] !== 'TAGS') return [item['filterCategory'], item] + else return [item['filterValue'], item] + }), + ).values(), + ]) + resetToFirstPage() + resetForm() }} > - - - Search: - - - - setNewDomainUrl(e.target.value)} - /> - + {({ handleChange, handleSubmit, errors, values }) => { + return ( + + + + Filters: + + + + + {errors.comparison} + + + + + + {errors.comparison} + + + + + + {errors.filterValue} + + + + + + ) + }} + + + + + + - - - - {adminDomainList} - + {showUpdateList ? ( + { + return { id, domain, tags: claimTags.map(({ label }) => label) } + })} + filters={filters} + search={debouncedSearchTerm} + domainCount={totalCount} + resetToFirstPage={resetToFirstPage} + /> + ) : ( + + )} + + + + ) + + return ( + + +
{ + e.preventDefault() // prevents page from refreshing + setModalProps({ + archived: false, + mutation: 'create', + tagInputList: [], + editingDomainId: '', + editingDomainUrl: newDomainUrl, + orgCount: 0, + }) + updateOnOpen() + }} + > + + + Search: + + + + { + setNewDomainUrl(e.target.value) + resetToFirstPage() + }} + /> + + + + +
+ + {domainSearchTip} + + + +
+ + + + {adminDomainList} { + updateOnClose() + resetToFirstPage() + } + : updateOnClose + } validationSchema={createValidationSchema(['domainUrl', 'selectors'])} orgId={orgId} orgSlug={orgSlug} - selectorInputList={selectorInputList} - editingDomainId={editingDomainId} - editingDomainUrl={editingDomainUrl} - mutation={mutation} + availableTags={availableTags} + {...modalProps} /> - - + - - Remove Domain - - - - - - Confirm removal of domain: - - {selectedRemoveDomainUrl} - - + { + removeDomain({ + variables: { + domainId: selectedRemoveProps.domainId, + orgId: orgId, + reason: values.reason, + }, + }) + }} + > + {({ values, handleSubmit, isSubmitting, handleChange }) => ( +
+ + Remove Domain + + + + + + Confirm removal of domain: + + {selectedRemoveProps.domain} - - - + + + A domain may only be removed for one of the reasons below. For a domain to no longer exist, it + must be removed from the DNS. If you need to remove this domain for a different reason, please + contact TBS Cyber Security. + + + + + + Reason + + + + + + + + + + + )} +
-
+ + + The "Asset State" describes how the domain relates to your organization. These states are used by Tracker to + give you a more accurate summary of your attack surface. + + + + + + + + + ) } AdminDomains.propTypes = { orgSlug: string.isRequired, orgId: string.isRequired, + verified: bool, + availableTags: array, domainsPerPage: number, + permission: string, } diff --git a/frontend/src/admin/AdminPage.js b/frontend/src/admin/AdminPage.js index 8fc59afd44..b9daed299d 100644 --- a/frontend/src/admin/AdminPage.js +++ b/frontend/src/admin/AdminPage.js @@ -1,25 +1,36 @@ -import React, { useCallback, useState } from 'react' -import { Button, Flex, Stack, Text, useToast } from '@chakra-ui/react' +import React, { useCallback, useState, useEffect } from 'react' +import { Button, Flex, Stack, Text, useToast, Select } from '@chakra-ui/react' import { AddIcon } from '@chakra-ui/icons' -import { t, Trans } from '@lingui/macro' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { useQuery } from '@apollo/client' -import { Link as RouteLink } from 'react-router-dom' +import { Link as RouteLink, useNavigate, useParams } from 'react-router-dom' import { useLingui } from '@lingui/react' import { AdminPanel } from './AdminPanel' import { OrganizationInformation } from './OrganizationInformation' -import { ADMIN_AFFILIATIONS, IS_USER_SUPER_ADMIN } from '../graphql/queries' +import { ADMIN_PAGE } from '../graphql/queries' import { Dropdown } from '../components/Dropdown' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' +import { bool, func, string } from 'prop-types' +import { SuperAdminUserList } from './SuperAdminUserList' +import { AuditLogTable } from './AuditLogTable' +import { ErrorBoundary } from 'react-error-boundary' +import withSuperAdmin from '../app/withSuperAdmin' +import { DomainTagsList } from './DomainTagsList' export default function AdminPage() { const [selectedOrg, setSelectedOrg] = useState('none') const [orgDetails, setOrgDetails] = useState({}) const [searchTerm, setSearchTerm] = useState('') const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('') + const [initRender, setInitRender] = useState(true) + + const { activeMenu } = useParams() const toast = useToast() + const navigate = useNavigate() const { i18n } = useLingui() const memoizedSetDebouncedSearchTermCallback = useCallback(() => { @@ -28,7 +39,7 @@ export default function AdminPage() { useDebouncedFunction(memoizedSetDebouncedSearchTermCallback, 500) - const { loading, error, data } = useQuery(ADMIN_AFFILIATIONS, { + const { loading, error, data } = useQuery(ADMIN_PAGE, { fetchPolicy: 'cache-and-network', nextFetchPolicy: 'cache-first', variables: { @@ -51,19 +62,21 @@ export default function AdminPage() { }, }) - const { data: isSA } = useQuery(IS_USER_SUPER_ADMIN, { - onError: (error) => { - const [_, message] = error.message.split(': ') - toast({ - title: 'Error', - description: message, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', + useEffect(() => { + if (!activeMenu) { + navigate(`/admin/organizations`, { replace: true }) + } + if (initRender && data?.findMyOrganizations?.edges.length === 1) { + setInitRender(false) + setOrgDetails({ + slug: data?.findMyOrganizations?.edges[0]?.node?.slug, + id: data?.findMyOrganizations?.edges[0]?.node?.id, + verified: data?.findMyOrganizations?.edges[0]?.node?.verified, + availableTags: data?.findMyOrganizations?.edges[0]?.node?.availableTags || [], }) - }, - }) + setSelectedOrg(data?.findMyOrganizations?.edges[0]?.node?.name || 'none') + } + }, [activeMenu, navigate, data]) if (error) { return @@ -86,11 +99,12 @@ export default function AdminPage() { } else { options = [] data.findMyOrganizations?.edges.forEach((edge) => { - const { slug, name, id } = edge.node - options.push({ label: name, value: { slug: slug, id: id } }) + const { slug, name, id, verified, availableTags } = edge.node + options.push({ label: name, value: { slug: slug, id: id, verified: verified, availableTags } }) }) dropdown = ( ) } - return ( - - + if (!data?.isUserAdmin) { + return ( + + + You currently have no admin affiliations. + + + + Search for your organization to request an invite + + + + + + Is your organization not using Tracker yet? + + + + + ) + } + + const changeActiveMenu = (val) => { + if (activeMenu !== val) { + navigate(`/admin/${val}`, { replace: true }) + } + } + + const orgPanel = ( + <> + {dropdown} + + + {logTable} + + + ) +} + +AuditLogTable.propTypes = { + orgId: string, +} diff --git a/frontend/src/admin/CvdEnrollmentForm.js b/frontend/src/admin/CvdEnrollmentForm.js new file mode 100644 index 0000000000..21641cc4d1 --- /dev/null +++ b/frontend/src/admin/CvdEnrollmentForm.js @@ -0,0 +1,265 @@ +import React from 'react' +import { t } from '@lingui/core/macro' +import { Trans } from '@lingui/react/macro' +import { + Box, + Flex, + FormControl, + FormLabel, + Select, + Text, + Popover, + PopoverTrigger, + PopoverContent, + PopoverHeader, + PopoverBody, + PopoverArrow, + PopoverCloseButton, + Divider, + Input, + Button, + PopoverFooter, + Link, +} from '@chakra-ui/react' +import { func, object, string } from 'prop-types' + +export function CvdEnrollmentForm({ handleChange, values, permission, ...rest }) { + return ( + + + + + CVD Enrollment Status + + + + + + + + + About Coordinated Vulnerability Disclosure (CVD) + + + + + 1. What is Coordinated Vulnerability Disclosure (CVD)? +
A structured process that allows security researchers to report vulnerabilities safely and + responsibly. It ensures findings are received, validated, and addressed in an organized way, + helping organizations fix issues before they can be exploited. +
+
+ + + + 2. Why enroll your domains? +
+ Enrolling your internet‑facing assets ensures researchers can report real vulnerabilities directly + to the Government of Canada through an approved and safe channel. This improves early detection, + reduces security risk, and strengthens your organization’s ability to respond quickly and + consistently. +
+
+ + + + 3. Which domains should you enroll? +
+ Enroll any public, internet‑facing domains or subdomains your organization owns—especially + production systems that deliver services or expose application functionality. Test or pre‑launch + environments may be excluded unless they are publicly accessible. +
+
+
+ + + +
+
+
+
+ +
+ + {values.cvdEnrollment.status !== 'NOT_ENROLLED' && ( + <> + + + + Description + + + + + + + + + Max Severity + + + + + + + + + Confidentiality Requirement + + + + + + + + + Integrity Requirement + + + + + + + + + Availability Requirement + + + + + + )} +
+ ) +} + +CvdEnrollmentForm.propTypes = { + values: object, + permission: string, + handleChange: func, +} diff --git a/frontend/src/admin/DomainTagsList.js b/frontend/src/admin/DomainTagsList.js new file mode 100644 index 0000000000..28f9ec0f39 --- /dev/null +++ b/frontend/src/admin/DomainTagsList.js @@ -0,0 +1,176 @@ +import React, { useState } from 'react' +import { Badge, Box, Button, Collapse, Flex, IconButton, Switch, Text, useToast } from '@chakra-ui/react' +import { DOMAIN_TAGS } from '../graphql/queries' +import { useQuery } from '@apollo/client' +import { EditIcon, PlusSquareIcon, ViewIcon, ViewOffIcon } from '@chakra-ui/icons' +import { LoadingMessage } from '../components/LoadingMessage' +import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' +import { TagForm } from './TagForm' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { string } from 'prop-types' + +export const DomainTagsList = ({ orgId, createOwnership }) => { + const [tagFormState, setTagFormState] = useState({ editingTags: {}, isCreatingTag: false }) + const [onlyVisible, setOnlyVisible] = useState(true) + const toast = useToast() + + const { loading, error, data } = useQuery(DOMAIN_TAGS, { + variables: { orgId, isVisible: onlyVisible }, + errorPolicy: 'ignore', + onError: (error) => { + const [_, message] = error.message.split(': ') + toast({ + title: 'Error', + description: message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + }) + + if (loading) return + if (error) return + + const ownershipBadgeColour = (ownership) => { + switch (ownership) { + case 'GLOBAL': + return 'weak' + case 'ORG': + return 'info' + case 'PENDING': + return 'strong' + default: + return 'primary' + } + } + + const ownershipLabel = { + GLOBAL: t`Global`, + ORG: t`Organization`, + PENDING: t`Pending`, + } + + let tagList + if (data.findAllTags.length === 0) { + tagList = ( + + No Tags + + ) + } else { + tagList = data.findAllTags.map(({ tagId, label, description, isVisible, ownership, organizations }) => { + return ( + + + } + variant="primary" + onClick={() => + setTagFormState((prev) => ({ + ...prev, + editingTags: { ...prev.editingTags, [tagId]: !prev.editingTags[tagId] }, + })) + } + mr="2" + /> + + + + {label.toUpperCase()} + + {organizations && ownership !== 'GLOBAL' && ( + + ({organizations.map(({ acronym }) => acronym).join(', ')}) + + )} + {!isVisible && } + + + {description} + + + {ownershipLabel[ownership]} + + + + + + + + ) + }) + } + + return ( + + + + + + + { + setOnlyVisible(e.target.checked) + }} + /> + + + + + + + + + {tagList} + + ) +} + +DomainTagsList.propTypes = { + orgId: string, + createOwnership: string, +} diff --git a/frontend/src/admin/DomainUpdateList.js b/frontend/src/admin/DomainUpdateList.js new file mode 100644 index 0000000000..0c42f1eb2c --- /dev/null +++ b/frontend/src/admin/DomainUpdateList.js @@ -0,0 +1,385 @@ +import React, { useState, useRef } from 'react' +import { + Table, + Thead, + Tr, + Th, + Td, + Checkbox, + Box, + Button, + useDisclosure, + Drawer, + DrawerOverlay, + DrawerContent, + DrawerHeader, + DrawerBody, + DrawerFooter, + VisuallyHidden, + CheckboxGroup, + Tbody, + Text, + Flex, + useToast, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, +} from '@chakra-ui/react' +import { useMutation } from '@apollo/client' +import { array, func, number, string } from 'prop-types' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { UPDATE_DOMAINS_BY_DOMAIN_IDS, UPDATE_DOMAINS_BY_FILTERS } from '../graphql/mutations' + +export function DomainUpdateList({ orgId, domains, availableTags, filters, search, domainCount, resetToFirstPage }) { + const toast = useToast() + // selectedIds is global across all pages + const [selectedIds, setSelectedIds] = useState(new Set()) + // selectAllGlobal means all filtered domains (not just visible) are selected + const [selectAllGlobal, setSelectAllGlobal] = useState(false) + const [tags, setTags] = useState([]) + const { isOpen, onOpen, onClose } = useDisclosure() + const { isOpen: isConfirmOpen, onOpen: onConfirmOpen, onClose: onConfirmClose } = useDisclosure() + + const resetSelections = () => { + setSelectedIds(new Set()) + setSelectAllGlobal(false) + setTags([]) + } + + const [updateDomainsByDomainIds, { loading: idLoading }] = useMutation(UPDATE_DOMAINS_BY_DOMAIN_IDS, { + refetchQueries: ['PaginatedOrgDomains', 'FindAuditLogs'], + onError(error) { + toast({ + title: t`An error occurred.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ updateDomainsByDomainIds }) { + if (updateDomainsByDomainIds.result.__typename === 'DomainBulkResult') { + onClose() + resetSelections() + resetToFirstPage() + toast({ + title: t`Domains updated.`, + description: updateDomainsByDomainIds.result.status, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else if (updateDomainsByDomainIds.result.__typename === 'DomainError') { + toast({ + title: t`Unable to update domains.`, + description: updateDomainsByDomainIds.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: t`Incorrect send method received.`, + description: t`Incorrect updateDomainsByDomainIds.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect updateDomainsByDomainIds.result typename.') + } + }, + }) + const [updateDomainsByFilters, { loading: filterLoading }] = useMutation(UPDATE_DOMAINS_BY_FILTERS, { + refetchQueries: ['PaginatedOrgDomains', 'FindAuditLogs'], + onError(error) { + toast({ + title: t`An error occurred.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ updateDomainsByFilters }) { + if (updateDomainsByFilters.result.__typename === 'DomainBulkResult') { + onClose() + resetSelections() + resetToFirstPage() + toast({ + title: t`Domains updated.`, + description: updateDomainsByFilters.result.status, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else if (updateDomainsByFilters.result.__typename === 'DomainError') { + toast({ + title: t`Unable to update domains.`, + description: updateDomainsByFilters.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: t`Incorrect send method received.`, + description: t`Incorrect updateDomainsByDomainIds.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect updateDomainsByFilters.result typename.') + } + }, + }) + + const liveRegionRef = useRef(null) + + // selection handlers + // Helper: get IDs of domains on current page + const currentPageIds = domains.map((d) => d.id) + // Helper: how many on this page are selected? + const selectedOnPage = domains.filter((d) => selectedIds.has(d.id)).length + // Helper: are all on this page selected? + const allOnPageSelected = domains.length > 0 && selectedOnPage === domains.length + // Helper: is some but not all on this page selected? + const someOnPageSelected = selectedOnPage > 0 && !allOnPageSelected + + const toggleDomain = (id) => { + let newSet = new Set(selectedIds) + if (newSet.has(id)) { + newSet.delete(id) + } else { + newSet.add(id) + } + setSelectedIds(newSet) + setSelectAllGlobal(false) + + if (liveRegionRef.current) { + if (newSet.size === 0) { + liveRegionRef.current.textContent = 'Selection cleared.' + } else if (allOnPageSelected) { + liveRegionRef.current.textContent = `All ${domains.length} domains on this page are selected.` + } else { + liveRegionRef.current.textContent = `${selectedOnPage} selected on this page.` + } + } + } + + const handleSelectAllPage = () => { + if (allOnPageSelected) { + // Deselect all on this page only + const newSet = new Set(selectedIds) + currentPageIds.forEach((id) => newSet.delete(id)) + setSelectedIds(newSet) + setSelectAllGlobal(false) + if (liveRegionRef.current) { + liveRegionRef.current.textContent = 'Selection cleared.' + } + } else { + // Add all on this page to selection (preserve others) + const newSet = new Set(selectedIds) + currentPageIds.forEach((id) => newSet.add(id)) + setSelectedIds(newSet) + setSelectAllGlobal(false) + if (liveRegionRef.current) { + liveRegionRef.current.textContent = `All ${domains.length} domains on this page are selected.` + } + } + } + + const handleSelectAllGlobal = () => { + setSelectAllGlobal(true) + // Add all visible to selectedIds (for UI feedback) + const newSet = new Set(selectedIds) + currentPageIds.forEach((id) => newSet.add(id)) + setSelectedIds(newSet) + if (liveRegionRef.current) { + liveRegionRef.current.textContent = `All ${domainCount} domains are selected.` + } + } + + const handleConfirmSubmit = async () => { + if (selectAllGlobal) { + await updateDomainsByFilters({ + variables: { filters, search, tags, orgId }, + }) + } else { + await updateDomainsByDomainIds({ + variables: { domainIds: Array.from(selectedIds), tags, orgId }, + }) + } + onConfirmClose() + } + + // Render all rows + const rows = domains.map((d) => { + const isChecked = selectedIds.has(d.id) + return ( + + + toggleDomain(d.id)} /> + + {d.domain} + {d.tags.join(', ').toUpperCase()} + + ) + }) + + return ( + + + + + + + + + + {rows} +
+ + + + + + Domain + + Current Tags +
+ + {/* Selection banner */} + {selectedOnPage > 0 && !selectAllGlobal && ( + + + + {allOnPageSelected ? ( + All {domains.length} domains on this page are selected. + ) : ( + {selectedOnPage} selected on this page. + )} + + {domainCount > domains.length && !selectAllGlobal && ( + + )} + + + )} + {selectAllGlobal && ( + + + + All {domainCount} domains are selected. Updates will apply to all filtered domains, not just this page. + + + + )} + + {/* Show number selected (global) */} + {selectedIds.size > 0 && !selectAllGlobal && ( + + {selectedIds.size} selected in total + + )} + {selectAllGlobal && ( + + All {domainCount} selected + + )} + + + + + + + + Apply Tags + + + setTags(values)}> + {availableTags.map(({ label, tagId }) => { + return ( + + + {label.toUpperCase()} + + + ) + })} + + + + + + + + + {/* Confirmation Modal */} + + + + + Are you sure? + + + + This will update {selectAllGlobal ? domainCount : selectedIds.size} domain(s). + + + Are you sure you want to apply these tag changes? + + + + + + + + + + +
+
+
+ ) +} + +DomainUpdateList.propTypes = { + domains: array, + filters: array, + availableTags: array, + orgId: string, + search: string, + domainCount: number, + resetToFirstPage: func, +} diff --git a/frontend/src/admin/OrganizationInformation.js b/frontend/src/admin/OrganizationInformation.js index 7659c56613..5ccf183c28 100644 --- a/frontend/src/admin/OrganizationInformation.js +++ b/frontend/src/admin/OrganizationInformation.js @@ -20,9 +20,10 @@ import { useToast, } from '@chakra-ui/react' import { CheckCircleIcon, MinusIcon, EditIcon } from '@chakra-ui/icons' -import { func, string } from 'prop-types' +import { bool, func, string } from 'prop-types' import { useMutation, useQuery } from '@apollo/client' -import { t, Trans } from '@lingui/macro' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { Formik } from 'formik' import { ORGANIZATION_INFORMATION } from '../graphql/queries' @@ -30,22 +31,12 @@ import { REMOVE_ORGANIZATION, UPDATE_ORGANIZATION } from '../graphql/mutations' import { FormField } from '../components/fields/FormField' import { LoadingMessage } from '../components/LoadingMessage' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' -import { - getRequirement, - schemaToValidation, -} from '../utilities/fieldRequirements' - -export function OrganizationInformation({ - orgSlug, - removeOrgCallback: setSelectedOrg, - ...props -}) { +import { getRequirement, schemaToValidation } from '../utilities/fieldRequirements' +import withSuperAdmin from '../app/withSuperAdmin' + +export function OrganizationInformation({ orgSlug, removeOrgCallback: setSelectedOrg, ...props }) { const toast = useToast() - const { - isOpen: isRemovalOpen, - onOpen: onRemovalOpen, - onClose: onRemovalClose, - } = useDisclosure() + const { isOpen: isRemovalOpen, onOpen: onRemovalOpen, onClose: onRemovalClose } = useDisclosure() const removeOrgBtnRef = useRef() const [isEditingOrg, setIsEditingOrg] = useState(false) @@ -66,118 +57,105 @@ export function OrganizationInformation({ }, }) - const [removeOrganization, { loading: removeOrgLoading }] = useMutation( - REMOVE_ORGANIZATION, - { - onError: ({ message }) => { + const [removeOrganization, { loading: removeOrgLoading }] = useMutation(REMOVE_ORGANIZATION, { + onError: ({ message }) => { + toast({ + title: t`An error occurred while removing this organization.`, + description: message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ removeOrganization }) { + // eslint-disable-next-line no-empty + if (removeOrganization.result.__typename === 'OrganizationResult') { + } else if (removeOrganization.result.__typename === 'OrganizationError') { toast({ - title: t`An error occurred while removing this organization.`, - description: message, + title: t`Unable to remove this organization.`, + description: removeOrganization.result.description, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ removeOrganization }) { - // eslint-disable-next-line no-empty - if (removeOrganization.result.__typename === 'OrganizationResult') { - } else if ( - removeOrganization.result.__typename === 'OrganizationError' - ) { - toast({ - title: t`Unable to remove this organization.`, - description: removeOrganization.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else { - toast({ - title: t`Incorrect typename received.`, - description: t`Incorrect removeOrganization.result typename.`, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - console.log('Incorrect removeOrganization.result typename.') - } - }, - update: (cache, { data }) => { - if (data.removeOrganization.result.__typename !== 'OrganizationResult') - return - + } else { toast({ - title: t`Removed Organization`, - description: t`You have successfully removed ${data.removeOrganization.result.organization.name}.`, - status: 'success', + title: t`Incorrect typename received.`, + description: t`Incorrect removeOrganization.result typename.`, + status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) + console.log('Incorrect removeOrganization.result typename.') + } + }, + update: (cache, { data }) => { + if (data.removeOrganization.result.__typename !== 'OrganizationResult') return - const removedOrgId = cache.identify( - data.removeOrganization.result.organization, - ) + toast({ + title: t`Removed Organization`, + description: t`You have successfully removed ${data.removeOrganization.result.organization.name}.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) - // Set admin page org to none, as the current is removed + setSelectedOrg('none') + + cache.evict({ id: removedOrgId }) }, - ) + }) - const [updateOrganization, { loading: updateOrgLoading }] = useMutation( - UPDATE_ORGANIZATION, - { - onError: ({ message }) => { + const [updateOrganization, { loading: updateOrgLoading }] = useMutation(UPDATE_ORGANIZATION, { + onError: ({ message }) => { + toast({ + title: t`An error occurred while updating this organization.`, + description: message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ updateOrganization }) { + if (updateOrganization.result.__typename === 'Organization') { toast({ - title: t`An error occurred while updating this organization.`, - description: message, + title: t`Updated Organization`, + description: t`You have successfully updated ${updateOrganization.result.name}.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else if (updateOrganization.result.__typename === 'OrganizationError') { + toast({ + title: t`Unable to update this organization.`, + description: updateOrganization.result.description, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ updateOrganization }) { - if (updateOrganization.result.__typename === 'Organization') { - toast({ - title: t`Updated Organization`, - description: t`You have successfully updated ${updateOrganization.result.name}.`, - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else if ( - updateOrganization.result.__typename === 'OrganizationError' - ) { - toast({ - title: t`Unable to update this organization.`, - description: updateOrganization.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else { - toast({ - title: t`Incorrect typename received.`, - description: t`Incorrect updateOrganization.result typename.`, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - console.log('Incorrect updateOrganization.result typename.') - } - }, + } else { + toast({ + title: t`Incorrect typename received.`, + description: t`Incorrect updateOrganization.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect updateOrganization.result typename.') + } }, - ) + }) if (loading) { return ( @@ -199,10 +177,7 @@ export function OrganizationInformation({ }) const removeOrgValidationSchema = schemaToValidation({ - orgName: getRequirement('field').matches( - org.name, - t`Organization name does not match.`, - ), + orgName: getRequirement('field').matches(org.name, t`Organization name does not match.`), }) return ( @@ -219,10 +194,7 @@ export function OrganizationInformation({ fontSize="3xl" > - {org.name}{' '} - {org.verified && ( - - )} + {org.name} {org.verified && }
@@ -268,6 +240,7 @@ export function OrganizationInformation({ provinceFR: '', cityEN: '', cityFR: '', + externalId: '', }} validationSchema={updateOrgValidationSchema} onSubmit={async (values, formikHelpers) => { @@ -304,10 +277,7 @@ export function OrganizationInformation({ }) // Close and reset form if successfully updated organization - if ( - updateResponse.data.updateOrganization.result.__typename === - 'Organization' - ) { + if (updateResponse.data.updateOrganization.result.__typename === 'Organization') { setIsEditingOrg(false) formikHelpers.resetForm() } @@ -322,23 +292,10 @@ export function OrganizationInformation({ mx="1rem" mb="1.5rem" > - - - Blank fields will not be included when updating the - organization. - + + Blank fields will not be included when updating the organization. - - - - - - + @@ -360,12 +317,10 @@ export function OrganizationInformation({ - + + @@ -529,7 +455,29 @@ export function OrganizationInformation({ ) } +const AcronymFields = withSuperAdmin(() => { + return ( + <> + + + + + + + + ) +}) + +const ExternalIdField = withSuperAdmin(() => { + return ( + + + + ) +}) + OrganizationInformation.propTypes = { orgSlug: string.isRequired, removeOrgCallback: func.isRequired, + isUserSuperAdmin: bool, } diff --git a/frontend/src/admin/SuperAdminUserList.js b/frontend/src/admin/SuperAdminUserList.js new file mode 100644 index 0000000000..c559bddbed --- /dev/null +++ b/frontend/src/admin/SuperAdminUserList.js @@ -0,0 +1,438 @@ +import React, { useCallback, useState } from 'react' +import { + Accordion, + AccordionButton, + AccordionItem, + AccordionPanel, + Badge, + Box, + Button, + Flex, + IconButton, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Stack, + Text, + useDisclosure, + useToast, +} from '@chakra-ui/react' + +import { FIND_MY_USERS } from '../graphql/queries' +import { CLOSE_ACCOUNT_OTHER } from '../graphql/mutations' +import { LoadingMessage } from '../components/LoadingMessage' +import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' +import { RelayPaginationControls } from '../components/RelayPaginationControls' +import { usePaginatedCollection } from '../utilities/usePaginatedCollection' +import { useDebouncedFunction } from '../utilities/useDebouncedFunction' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { CheckCircleIcon, EditIcon, MinusIcon } from '@chakra-ui/icons' +import { SearchBox } from '../components/SearchBox' +import { UserListModal } from './UserListModal' +import { FormField } from '../components/fields/FormField' +import { createValidationSchema } from '../utilities/fieldRequirements' +import { Formik } from 'formik' +import { useMutation } from '@apollo/client' + +export function SuperAdminUserList() { + const [orderDirection, setOrderDirection] = useState('ASC') + const [orderField, setOrderField] = useState('USER_USERNAME') + const [searchTerm, setSearchTerm] = useState('') + const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('') + const [usersPerPage, setUsersPerPage] = useState(50) + const [editUserRole, setEditUserRole] = useState({ + mutation: '', + userName: '', + displayName: '', + userId: '', + userRole: '', + orgName: '', + orgId: '', + }) + + const toast = useToast() + const { isOpen, onOpen, onClose } = useDisclosure() + const { isOpen: closeAccountIsOpen, onOpen: closeAccountOnOpen, onClose: closeAccountOnClose } = useDisclosure() + + const memoizedSetDebouncedSearchTermCallback = useCallback(() => { + setDebouncedSearchTerm(searchTerm) + }, [searchTerm]) + + useDebouncedFunction(memoizedSetDebouncedSearchTermCallback, 500) + + const [closeAccount, { loading: loadingCloseAccount }] = useMutation(CLOSE_ACCOUNT_OTHER, { + refetchQueries: ['FindMyUsers'], + update(cache, { data: { closeAccountOther } }) { + if (closeAccountOther.result.__typename === 'CloseAccountResult') { + cache.evict({ id: cache.identify(closeAccountOther.result.user) }) + } + }, + onError(error) { + toast({ + title: t`Unable to close this account.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ closeAccountOther }) { + if (closeAccountOther.result.__typename === 'CloseAccountResult') { + toast({ + title: t`Account Closed Successfully`, + description: t`Tracker account has been successfully closed.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + closeAccountOnClose() + } else if (closeAccountOther.result.__typename === 'CloseAccountError') { + toast({ + title: t`Unable to close the account.`, + description: closeAccountOther.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: t`Incorrect send method received.`, + description: t`Incorrect closeAccount.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } + }, + }) + + const { + loading, + isLoadingMore, + error, + nodes, + next, + previous, + resetToFirstPage, + hasNextPage, + hasPreviousPage, + totalCount, + } = usePaginatedCollection({ + fetchForward: FIND_MY_USERS, + recordsPerPage: usersPerPage, + relayRoot: 'findMyUsers', + variables: { + orderBy: { field: orderField, direction: orderDirection }, + search: debouncedSearchTerm, + }, + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first', + errorPolicy: 'ignore', // allow partial success + }) + + if (error) return + + const orderByOptions = [ + { value: 'USER_USERNAME', text: t`Email` }, + { value: 'USER_DISPLAYNAME', text: t`Display Name` }, + { value: 'USER_EMAIL_VALIDATED', text: t`Verified` }, + { value: 'USER_INSIDER', text: t`Inside User` }, + ] + + const userList = + loading || isLoadingMore ? ( + + User List + + ) : nodes.length === 0 ? ( + + No users + + ) : ( + nodes.map(({ id: userId, userName, displayName, emailValidated, insideUser, affiliations }) => { + const { totalCount, edges: orgEdges } = affiliations + const orgNodes = orgEdges?.map((e) => e.node) + let userAffiliations + if (totalCount === 0) { + userAffiliations = ( + + + This user is not affiliated with any organizations + + + ) + } else { + userAffiliations = orgNodes.map(({ permission: userRole, organization }, idx) => { + if (!organization) { + return ( + + + An error occurred when fetching this organization's information + + + ) + } + const { id: orgId, name: orgName, acronym, slug, verified } = organization + return ( + + + { + setEditUserRole({ + mutation: 'remove', + userId, + userName, + userRole, + orgName, + orgId, + }) + onOpen() + }} + p="2" + icon={} + /> + { + setEditUserRole({ + mutation: 'update', + userId, + userName, + userRole, + orgName, + orgId, + }) + onOpen() + }} + p="2" + icon={} + /> + + + + {orgName} ({acronym}){' '} + {verified && ( + + )} + + + {userRole} + + + + + ) + }) + } + + return ( + + + + + + {userName} + {displayName} + + + Verified + + {insideUser && ( + + Inside User + + )} + + + Affiliations: {totalCount} + + + + + + {userAffiliations} + + + ) + }) + ) + + return ( + + + {userList} + + + { + await closeAccount({ + variables: { userId: editUserRole.userId }, + }) + }} + > + {({ handleSubmit }) => ( +
+ + + + Close Account + + + + + This action CANNOT be reversed, are you sure you wish to to close the account{' '} + {editUserRole.displayName}? + + + + + Enter "{editUserRole.userName}" below to confirm removal. This field is case-sensitive. + + + + + + + + + + + + + + )} +
+
+
+ ) +} diff --git a/frontend/src/admin/TagForm.js b/frontend/src/admin/TagForm.js new file mode 100644 index 0000000000..41603dc552 --- /dev/null +++ b/frontend/src/admin/TagForm.js @@ -0,0 +1,289 @@ +import React from 'react' +import { Badge, Box, Button, Flex, FormLabel, Grid, Select, Switch, Text, useToast } from '@chakra-ui/react' +import { useMutation } from '@apollo/client' +import { CREATE_TAG, UPDATE_TAG } from '../graphql/mutations' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { Formik } from 'formik' +import { FormField } from '../components/fields/FormField' +import { getRequirement, schemaToValidation } from '../utilities/fieldRequirements' +import { bool, string, func } from 'prop-types' +import withSuperAdmin from '../app/withSuperAdmin' + +export function TagForm({ mutation, tagId = '', visible = true, ownership, setTagFormState, orgId }) { + const toast = useToast() + + const fieldRequirement = getRequirement('field') + const validationSchema = schemaToValidation({ + labelEn: fieldRequirement, + labelFr: fieldRequirement, + isVisible: fieldRequirement, + ownership: fieldRequirement, + }) + + const [updateTag, { loading: updateLoading }] = useMutation(UPDATE_TAG, { + refetchQueries: ['FindAllTags'], + onError(error) { + toast({ + title: t`An error occurred.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ updateTag }) { + if (updateTag.result.__typename === 'Tag') { + toast({ + title: t`Tag updated`, + description: t`${updateTag.result.tagId} was successfully updated.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else if (updateTag.result.__typename === 'TagError') { + toast({ + title: t`Unable to update tag.`, + description: updateTag.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: t`Incorrect send method received.`, + description: `Incorrect updateTag.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect updateTag.result typename.') + } + }, + }) + + const [createTag, { loading: createLoading }] = useMutation(CREATE_TAG, { + refetchQueries: ['FindAllTags'], + onError(error) { + toast({ + title: t`An error occurred.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ createTag }) { + if (createTag.result.__typename === 'Tag') { + toast({ + title: t`Tag created`, + description: t`${createTag.result.tagId} was added to tag list.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + setTagFormState((prev) => ({ ...prev, isCreatingTag: false })) + } else if (createTag.result.__typename === 'TagError') { + toast({ + title: t`Unable to create new tag.`, + description: createTag.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: t`Incorrect send method received.`, + description: `Incorrect createTag.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect createTag.result typename.') + } + }, + }) + + const isLoading = createLoading || updateLoading + + const initial = { + labelEn: '', + labelFr: '', + descriptionEn: '', + descriptionFr: '', + isVisible: visible, + ownership, + orgId, + } + + return ( + + + + Note: Tags must not include data that would reclassify the asset above{' '} + Unclassified, Low Integrity, Low Availability (ULL) + + + { + if (mutation === 'create') { + await createTag({ variables: values }) + } else if (mutation === 'update') { + // Only include fields that have changed from initial values + const propertiesWithValues = {} + Object.entries(values).forEach(([key, value]) => { + // Only include if value is different from initial + if (value !== initial[key] && value !== '' && value !== undefined) { + propertiesWithValues[key] = value + } + }) + + // Handle case where user does not supply any fields to update + if (Object.keys(propertiesWithValues).length === 0) { + toast({ + title: t`Tag not updated`, + description: t`No values were supplied when attempting to update organization details.`, + status: 'warning', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + + return + } + + const updateResponse = await updateTag({ + variables: { + tagId, + orgId, + ...propertiesWithValues, + }, + }) + // Close and reset form if successfully updated organization + if (updateResponse.data.updateTag.result.__typename === 'Tag') { + setTagFormState((prev) => ({ + ...prev, + editingTags: { ...prev.editingTags, [tagId]: false }, + })) + formikHelpers.resetForm() + } + } + }} + > + {({ handleSubmit, handleReset, handleChange }) => ( +
+ + + + + + + + + + + + + + + + + + Visible + + + + + + + + +
+ )} +
+
+ ) +} + +const OwnershipSelect = withSuperAdmin(({ ownership, handleChange, orgId }) => { + return ( + + + + Ownership: + + + + + ) +}) + +TagForm.propTypes = { + mutation: string, + tagId: string, + visible: bool, + ownership: string, + setTagFormState: func, + orgId: string, +} diff --git a/frontend/src/admin/UserList.js b/frontend/src/admin/UserList.js index c5ec5c924d..f8230ddcb9 100644 --- a/frontend/src/admin/UserList.js +++ b/frontend/src/admin/UserList.js @@ -8,26 +8,16 @@ import { Input, InputGroup, InputLeftElement, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - Stack, Text, useDisclosure, - useToast, } from '@chakra-ui/react' import { AddIcon, EditIcon, EmailIcon, MinusIcon } from '@chakra-ui/icons' -import { t, Trans } from '@lingui/macro' -import { number, string } from 'prop-types' -import { useMutation } from '@apollo/client' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { string } from 'prop-types' import { UserListModal } from './UserListModal' -import { REMOVE_USER_FROM_ORG } from '../graphql/mutations' import { PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE as FORWARD } from '../graphql/queries' import { UserCard } from '../components/UserCard' import { LoadingMessage } from '../components/LoadingMessage' @@ -35,29 +25,19 @@ import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { RelayPaginationControls } from '../components/RelayPaginationControls' import { usePaginatedCollection } from '../utilities/usePaginatedCollection' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' +import { bool } from 'prop-types' -export function UserList({ permission, orgSlug, usersPerPage, orgId }) { - const toast = useToast() +export function UserList({ includePending, permission, orgSlug, orgId }) { const [mutation, setMutation] = useState() const [addedUserName, setAddedUserName] = useState('') const [selectedRemoveUser, setSelectedRemoveUser] = useState({ id: null, userName: null, }) - - const { - isOpen: removeIsOpen, - onOpen: removeOnOpen, - onClose: removeOnClose, - } = useDisclosure() - + const [usersPerPage, setUsersPerPage] = useState(50) const [editingUserRole, setEditingUserRole] = useState() const [editingUserName, setEditingUserName] = useState() - const { - isOpen: updateIsOpen, - onOpen: updateOnOpen, - onClose: updateOnClose, - } = useDisclosure() + const { isOpen, onOpen, onClose } = useDisclosure() const [debouncedSearchUser, setDebouncedSearchUser] = useState('') @@ -76,56 +56,22 @@ export function UserList({ permission, orgSlug, usersPerPage, orgId }) { previous, hasNextPage, hasPreviousPage, + resetToFirstPage, + totalCount, } = usePaginatedCollection({ fetchForward: FORWARD, recordsPerPage: usersPerPage, - variables: { orgSlug, search: debouncedSearchUser }, + variables: { + orgSlug, + search: debouncedSearchUser, + includePending, + orderBy: { field: 'PERMISSION', direction: 'ASC' }, + }, relayRoot: 'findOrganizationBySlug.affiliations', fetchPolicy: 'cache-and-network', nextFetchPolicy: 'cache-first', }) - const [removeUser, { loading: removeUserLoading }] = useMutation( - REMOVE_USER_FROM_ORG, - { - refetchQueries: ['PaginatedOrgAffiliations'], - awaitRefetchQueries: true, - - onError(error) { - toast({ - title: t`An error occurred.`, - description: error.message, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - }, - onCompleted({ removeUserFromOrg }) { - if (removeUserFromOrg.result.__typename === 'RemoveUserFromOrgResult') { - removeOnClose() - toast({ - title: t`User removed.`, - description: t`Successfully removed user ${removeUserFromOrg.result.user.userName}.`, - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else if (removeUserFromOrg.result.__typename === 'AffiliationError') { - toast({ - title: t`Unable to remove user.`, - description: removeUserFromOrg.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } - }, - }, - ) - if (error) return const userList = loading ? ( @@ -137,161 +83,127 @@ export function UserList({ permission, orgSlug, usersPerPage, orgId }) { No users
) : ( - nodes.map((node) => { - const userRole = node.permission + nodes.map(({ id, permission: userRole, user }) => { return ( - - - - { - setSelectedRemoveUser(node.user) - removeOnOpen() - }} - p={2} - m={0} - icon={} - /> - { - setEditingUserRole(userRole) - setEditingUserName(node.user.userName) - setMutation('update') - updateOnOpen() - }} - p={2} - m={0} - icon={} - /> - - - - - + + { + setSelectedRemoveUser(user) + setMutation('remove') + onOpen() + }} + px="2" + mr="1" + icon={} + /> + { + setEditingUserRole(userRole) + setEditingUserName(user.userName) + setMutation('update') + onOpen() + }} + px="2" + mr="2" + icon={} + /> + ) }) ) return ( -
{ - e.preventDefault() // prevents page from refreshing - setMutation('create') - setEditingUserRole('USER') - setEditingUserName(addedUserName) - updateOnOpen() - }} - > - + { + e.preventDefault() // prevents page from refreshing + setMutation('create') + setEditingUserRole('USER') + setEditingUserName(addedUserName) + onOpen() + }} > - - Search: - - - - setAddedUserName(e.target.value)} - /> - - - - -
+ + + Search: + + + + setAddedUserName(e.target.value)} + /> + + + + + + + {userList} - - - - - Remove User - - - - - - Confirm removal of user: - - {selectedRemoveUser.userName} - - - - - - - - - + {mutation === 'update' ? ( Edit User + ) : mutation === 'remove' ? ( + Remove User ) : ( Add User )} - {mutation === 'update' ? ( + {mutation === 'create' ? ( + + ) : ( User: {editingUserName} - ) : ( - )} - - - Role: - - - + {orgName && ( + + + Organization: + + {orgName} + + )} + {mutation !== 'remove' && ( + + + Role: + + + + )} - @@ -248,7 +292,9 @@ UserListModal.propTypes = { orgId: string, editingUserRole: string, editingUserName: string, + editingUserId: string, orgSlug: string, + orgName: string, mutation: string, permission: string, } diff --git a/frontend/src/admin/__tests__/AdminDomainCard.test.js b/frontend/src/admin/__tests__/AdminDomainCard.test.js index 528017800d..46d1eb1d9f 100644 --- a/frontend/src/admin/__tests__/AdminDomainCard.test.js +++ b/frontend/src/admin/__tests__/AdminDomainCard.test.js @@ -3,35 +3,49 @@ import { render, waitFor } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { List, theme, ChakraProvider } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' - +import { i18n } from '@lingui/core' import { AdminDomainCard } from '../AdminDomainCard' +import { MockedProvider } from '@apollo/client/testing' +import { IS_USER_SUPER_ADMIN } from '../../graphql/queries' +import { UserVarProvider } from '../../utilities/userState' +import { makeVar } from '@apollo/client' -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: {}, +const mocks = [ + { + request: { + query: IS_USER_SUPER_ADMIN, + }, + result: { + data: { + isUserSuperAdmin: false, + }, + }, }, -}) +] describe('', () => { it('represents a domain', async () => { const { getByText } = render( - - - - - - - - - , + + + + + + + + + + + + + , ) await waitFor(() => { diff --git a/frontend/src/admin/__tests__/AdminDomains.test.js b/frontend/src/admin/__tests__/AdminDomains.test.js index dc78efdc36..b9118938ee 100644 --- a/frontend/src/admin/__tests__/AdminDomains.test.js +++ b/frontend/src/admin/__tests__/AdminDomains.test.js @@ -3,42 +3,28 @@ import { fireEvent, render, waitFor } from '@testing-library/react' import { theme, ChakraProvider } from '@chakra-ui/react' import { MemoryRouter } from 'react-router-dom' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { MockedProvider } from '@apollo/client/testing' import { makeVar } from '@apollo/client' import userEvent from '@testing-library/user-event' -import { en } from 'make-plural/plurals' - import { AdminDomains } from '../AdminDomains' - import { createCache } from '../../client' import { UserVarProvider } from '../../utilities/userState' -import { - rawOrgDomainListData, - rawOrgDomainListDataEmpty, -} from '../../fixtures/orgDomainListData' +import { rawOrgDomainListData, rawOrgDomainListDataEmpty } from '../../fixtures/orgDomainListData' import { PAGINATED_ORG_DOMAINS_ADMIN_PAGE as FORWARD } from '../../graphql/queries' -import { - CREATE_DOMAIN, - REMOVE_DOMAIN, - UPDATE_DOMAIN, -} from '../../graphql/mutations' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) +import { CREATE_DOMAIN, REMOVE_DOMAIN, UPDATE_DOMAIN } from '../../graphql/mutations' const mocks = [ { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListData }, }, @@ -48,9 +34,7 @@ describe('', () => { it('successfully renders with mocked data', async () => { const { getAllByText } = render( - + @@ -58,6 +42,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug="test-org.slug" domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -77,7 +62,13 @@ describe('', () => { { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListDataEmpty }, }, @@ -85,9 +76,7 @@ describe('', () => { const { getByText } = render( - + @@ -95,6 +84,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -118,7 +108,13 @@ describe('', () => { { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListData }, }, @@ -128,7 +124,8 @@ describe('', () => { variables: { orgId: 'gwdsfgvwsdgfvswefgdv', domain: 'test-domain.gc.ca', - selectors: [], + tags: [], + archived: false, }, }, result: { @@ -161,6 +158,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -172,9 +170,7 @@ describe('', () => { const addDomain = await findByText(/Add Domain/i) fireEvent.click(addDomain) - await waitFor(() => - expect(getByText(/Add Domain Details/)).toBeInTheDocument(), - ) + await waitFor(() => expect(getByText(/Add Domain Details/)).toBeInTheDocument()) const confirmBtn = getByText(/Confirm/) fireEvent.click(confirmBtn) @@ -190,7 +186,13 @@ describe('', () => { { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListData }, }, @@ -198,9 +200,11 @@ describe('', () => { request: { query: FORWARD, variables: { - first: 4, + first: 50, orgSlug: 'test-org.slug', - search: 'test-domain.gc.ca', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], }, }, result: { data: rawOrgDomainListData }, @@ -211,7 +215,10 @@ describe('', () => { variables: { orgId: 'testid=', domain: 'test-domain.gc.ca', - selectors: [], + tags: [], + archived: false, + assetState: 'APPROVED', + cvdEnrollment: { status: 'NOT_ENROLLED' }, }, }, result: { @@ -245,6 +252,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -262,16 +270,12 @@ describe('', () => { const addDomainButton = getByRole('button', { name: 'Add Domain' }) userEvent.click(addDomainButton) - await waitFor(() => - expect(getByText(/Add Domain Details/)).toBeInTheDocument(), - ) + await waitFor(() => expect(getByText(/Add Domain Details/)).toBeInTheDocument()) const confirmButton = getByRole('button', { name: /Confirm/ }) userEvent.click(confirmButton) - await waitFor(() => - expect(getByText(/Unable to create new domain./i)).toBeVisible(), - ) + await waitFor(() => expect(getByText(/Unable to create new domain./i)).toBeInTheDocument()) }) it('returns a success when valid URL is given', async () => { @@ -279,7 +283,13 @@ describe('', () => { { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListData }, }, @@ -287,9 +297,11 @@ describe('', () => { request: { query: FORWARD, variables: { - first: 4, + first: 50, orgSlug: 'test-org.slug', - search: 'test.domain.gc.ca', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], }, }, result: { data: rawOrgDomainListData }, @@ -300,7 +312,10 @@ describe('', () => { variables: { orgId: rawOrgDomainListData.findOrganizationBySlug.id, domain: 'test.domain.gc.ca', - selectors: [], + tags: [], + archived: false, + assetState: 'APPROVED', + cvdEnrollment: { status: 'NOT_ENROLLED' }, }, }, result: { @@ -317,18 +332,14 @@ describe('', () => { }, ] - const { - getByText, - getByPlaceholderText, - findByText, - queryByText, - } = render( + const { getByText, getByPlaceholderText, findByText, queryByText, findByRole } = render( @@ -338,6 +349,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -357,142 +369,49 @@ describe('', () => { userEvent.click(addDomainButton) - const confirmBtn = getByText(/Confirm/) - fireEvent.click(confirmBtn) + await waitFor(() => expect(getByText(/Add Domain Details/i)).toBeInTheDocument()) - await waitFor(() => expect(getByText(/Domain added/i)).toBeVisible()) + const assetStateSelect = await findByRole('combobox', { name: /Asset State/ }) - await waitFor(() => - expect(queryByText('Add Domain Details')).not.toBeInTheDocument(), - ) - }) - - it('succeeds when DKIM selectors are added', async () => { - const mocks = [ - { - request: { - query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, - }, - result: { data: rawOrgDomainListData }, - }, - { - request: { - query: CREATE_DOMAIN, - variables: { - orgId: rawOrgDomainListData.findOrganizationBySlug.id, - domain: 'test.domain.gc.ca', - selectors: ['selector1._domainkey'], - }, - }, - result: { - data: { - createDomain: { - result: { - domain: 'lauretta.name', - __typename: 'Domain', - }, - __typename: 'CreateDomainPayload', - }, - }, - }, + fireEvent.change(assetStateSelect, { + target: { + value: 'APPROVED', }, - ] - - const { - getByText, - getByTestId, - getByPlaceholderText, - queryAllByText, - findByText, - queryByText, - getByRole, - } = render( - - - - - - - - - - - , - ) - - const addDomainBtn = await findByText(/Add Domain/) - userEvent.click(addDomainBtn) - - await waitFor(() => - expect(getByText(/Add Domain Details/)).toBeInTheDocument(), - ) - - const domainInput = getByRole('textbox', { name: /New Domain URL/ }) - expect(domainInput).toBeInTheDocument() - userEvent.type(domainInput, 'test.domain.gc.ca') - - const addSelectorBtn = getByTestId(/add-dkim-selector/) - fireEvent.click(addSelectorBtn) - - const selectorInput = getByPlaceholderText(/DKIM Selector/) - fireEvent.blur(selectorInput) - - await waitFor(() => - expect(getByText(/Selector cannot be empty/)).toBeInTheDocument(), - ) - - fireEvent.change(selectorInput, { target: { value: 'selector1' } }) - - await waitFor(() => - expect( - getByText(/Selector must be string ending in '._domainkey'/), - ).toBeInTheDocument(), - ) - - fireEvent.change(selectorInput, { - target: { value: 'selector1._domainkey' }, }) const confirmBtn = getByText(/Confirm/) fireEvent.click(confirmBtn) - await waitFor(() => { - const successMessages = queryAllByText(/Domain added/i) - expect(successMessages[0]).toBeVisible() - }) + await waitFor(() => expect(getByText(/Domain added/i)).toBeInTheDocument()) - await waitFor(() => - expect(queryByText('Add Domain Details')).not.toBeInTheDocument(), - ) + await waitFor(() => expect(queryByText('Add Domain Details')).not.toBeInTheDocument()) }) }) - // TODO removeDomain mutation describe('removing a domain', () => { it('successfully removes domain from list', async () => { const mocks = [ { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListData }, }, { request: { query: REMOVE_DOMAIN, - variables: { domainId: 'testid2=', orgId: 'testid=' }, + variables: { + domainId: 'testid2=', + orgId: 'testid=', + reason: 'WRONG_ORG', + }, }, result: { data: { @@ -508,7 +427,7 @@ describe('', () => { }, ] - const { getByText, findByTestId, queryAllByText, queryByText } = render( + const { getByText, findByTestId } = render( ', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -544,26 +464,22 @@ describe('', () => { const confirm = getByText('Confirm') fireEvent.click(confirm) - - await waitFor(() => { - const removed = queryAllByText(/Domain removed/i) - expect(removed[0]).toBeVisible() - }) - - await waitFor(() => - expect(queryByText('Remove Domain')).not.toBeInTheDocument(), - ) }) }) - // TODO updateDomain mutation describe('editing a domain', () => { - it('successfully edits domain URL', async () => { + it('successfully edits domain details', async () => { const mocks = [ { request: { query: FORWARD, - variables: { first: 4, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: rawOrgDomainListData }, }, @@ -573,8 +489,10 @@ describe('', () => { variables: { domainId: 'testid2=', orgId: 'testid=', - domain: 'test.domain.ca', - selectors: [], + tags: [], + archived: false, + assetState: 'MONITOR_ONLY', + cvdEnrollment: { status: 'NOT_ENROLLED' }, }, }, result: { @@ -591,13 +509,14 @@ describe('', () => { }, ] - const { getByText, findByTestId, getByLabelText, queryByText } = render( + const { getByText, findByTestId, queryByText, findByRole } = render( @@ -607,6 +526,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -618,25 +538,22 @@ describe('', () => { const editDomainButton = await findByTestId('edit-1') userEvent.click(editDomainButton) - await waitFor(() => - expect(getByText(/Edit Domain Details/i)).toBeInTheDocument(), - ) + await waitFor(() => expect(getByText(/Edit Domain Details/i)).toBeInTheDocument()) + + const assetStateSelect = await findByRole('combobox', { name: /Asset State/ }) - const editDomainInput = getByLabelText(/Domain URL:/) - fireEvent.change(editDomainInput, { + fireEvent.change(assetStateSelect, { target: { - value: 'test.domain.ca', + value: 'MONITOR_ONLY', }, }) const confirm = getByText('Confirm') fireEvent.click(confirm) - await waitFor(() => expect(getByText(/Domain updated/)).toBeVisible()) + await waitFor(() => expect(getByText(/Domain updated/)).toBeInTheDocument()) - await waitFor(() => - expect(queryByText('Edit Domain Details')).not.toBeInTheDocument(), - ) + await waitFor(() => expect(queryByText('Edit Domain Details')).not.toBeInTheDocument()) }) }) }) diff --git a/frontend/src/admin/__tests__/AdminPage.test.js b/frontend/src/admin/__tests__/AdminPage.test.js index a42974571b..50ec096d92 100644 --- a/frontend/src/admin/__tests__/AdminPage.test.js +++ b/frontend/src/admin/__tests__/AdminPage.test.js @@ -1,39 +1,134 @@ import React from 'react' import { theme, ChakraProvider } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { MockedProvider } from '@apollo/client/testing' import AdminPage from '../AdminPage' import { waitFor, render } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { makeVar } from '@apollo/client' -import { en } from 'make-plural/plurals' import userEvent from '@testing-library/user-event' - import { UserVarProvider } from '../../utilities/userState' +import { TourProvider } from '../../userOnboarding/contexts/TourContext' + import { - ADMIN_AFFILIATIONS, - IS_USER_SUPER_ADMIN, + ADMIN_PAGE, ORGANIZATION_INFORMATION, PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE, PAGINATED_ORG_DOMAINS_ADMIN_PAGE, } from '../../graphql/queries' -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, +describe('', () => { + it('shows a list of the users organizations', async () => { + const { getByText } = render( + + + + + + + + + + + + + , + ) + + await waitFor(() => { + const welcome = getByText(/Select an organization to view admin options/i) + expect(welcome).toBeInTheDocument() + }) + }) + + it('displays info for admin', async () => { + const { getByText, findByRole } = render( + + + + + + + + + + + + + , + ) + + const organizationInput = await findByRole('textbox', { + name: /Organization/, + }) + userEvent.click(organizationInput) + + await waitFor(() => { + expect(getByText(/Wolf Group/)).toBeInTheDocument() + }) + const orgEntry = getByText(/Wolf Group/) + userEvent.click(orgEntry) + + await waitFor(() => { + expect(getByText(/Slug:/i)).toHaveTextContent(/Slug: Wolf-Group/i) + }) + }) + + it('filters organization list', async () => { + const { getByText, queryByText, findByRole } = render( + + + + + + + + + + + + + , + ) + + const organizationInput = await findByRole('textbox', { + name: /Organization/, + }) + + userEvent.click(organizationInput) + + await waitFor(() => { + expect(getByText(/Wolf Group/)).toBeInTheDocument() + expect(getByText(/Hane - Pollich/)).toBeInTheDocument() + }) + + userEvent.type(organizationInput, 'Wolf Group') + + await waitFor(() => { + expect(getByText(/Wolf Group/)).toBeInTheDocument() + expect(queryByText(/Hane - Pollich/)).not.toBeInTheDocument() + }) + }) }) -describe('', () => { - const mocks = [ +function mocks() { + return [ { request: { - query: ADMIN_AFFILIATIONS, + query: ADMIN_PAGE, variables: { first: 100, orderBy: { @@ -75,12 +170,14 @@ describe('', () => { }, ], }, + isUserSuperAdmin: true, + isUserAdmin: true, }, }, }, { request: { - query: ADMIN_AFFILIATIONS, + query: ADMIN_PAGE, variables: { first: 100, orderBy: { @@ -106,17 +203,8 @@ describe('', () => { }, ], }, - }, - }, - }, - { - request: { - query: IS_USER_SUPER_ADMIN, - variables: {}, - }, - result: { - data: { - isUserSuperAdmin: false, + isUserSuperAdmin: true, + isUserAdmin: true, }, }, }, @@ -146,7 +234,13 @@ describe('', () => { { request: { query: PAGINATED_ORG_DOMAINS_ADMIN_PAGE, - variables: { orgSlug: 'Wolf-Group', first: 10, search: '' }, + variables: { + first: 50, + orgSlug: 'Wolf-Group', + search: '', + orderBy: { field: 'DOMAIN', direction: 'ASC' }, + filters: [], + }, }, result: { data: { @@ -159,7 +253,8 @@ describe('', () => { node: { id: 'e86770be-b13e-4bee-b833-6a2e31add85c', domain: 'antonia.name', - selectors: ['selector9._domainkey', 'selector7._domainkey'], + lastRan: '2020-08-13T14:42:03.385294', + selectors: ['selector9', 'selector7'], __typename: 'Domain', }, __typename: 'DomainEdge', @@ -168,7 +263,8 @@ describe('', () => { node: { id: '11494bbf-1ed6-4edb-b96f-3fed6f36a226', domain: 'blaise.biz', - selectors: ['selector3._domainkey', 'selector5._domainkey'], + lastRan: '2020-08-13T14:42:03.385294', + selectors: ['selector3', 'selector5'], __typename: 'Domain', }, __typename: 'DomainEdge', @@ -192,7 +288,13 @@ describe('', () => { { request: { query: PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE, - variables: { orgSlug: 'Wolf-Group', first: 10, search: '' }, + variables: { + orgSlug: 'Wolf-Group', + first: 50, + search: '', + includePending: true, + orderBy: { field: 'PERMISSION', direction: 'ASC' }, + }, }, result: { data: { @@ -245,103 +347,4 @@ describe('', () => { }, }, ] - - it('renders correctly', async () => { - const { getByText } = render( - - - - - - - - - - - , - ) - - await waitFor(() => { - const welcome = getByText(/Select an organization to view admin options/i) - expect(welcome).toBeInTheDocument() - }) - }) - - describe('Organization select', () => { - it('displays info for admin', async () => { - const { getByText, findByRole } = render( - - - - - - - - - - - , - ) - - const organizationInput = await findByRole('textbox', { - name: /Organization/, - }) - userEvent.click(organizationInput) - - const orgEntry = getByText(/Wolf Group/) - userEvent.click(orgEntry) - - await waitFor(() => { - expect(getByText(/Slug:/i)).toHaveTextContent(/Slug: Wolf-Group/i) - }) - }) - - it('filters organization list', async () => { - const { getByText, queryByText, findByRole } = render( - - - - - - - - - - - , - ) - - const organizationInput = await findByRole('textbox', { - name: /Organization/, - }) - - userEvent.click(organizationInput) - - await waitFor(() => { - expect(getByText(/Wolf Group/)).toBeInTheDocument() - expect(getByText(/Hane - Pollich/)).toBeInTheDocument() - }) - - userEvent.type(organizationInput, 'Wolf Group') - - await waitFor(() => { - expect(getByText(/Wolf Group/)).toBeInTheDocument() - expect(queryByText(/Hane - Pollich/)).not.toBeInTheDocument() - }) - }) - }) -}) +} diff --git a/frontend/src/admin/__tests__/AdminPanel.test.js b/frontend/src/admin/__tests__/AdminPanel.test.js index 10e4df227d..c9ecfc7f06 100644 --- a/frontend/src/admin/__tests__/AdminPanel.test.js +++ b/frontend/src/admin/__tests__/AdminPanel.test.js @@ -2,32 +2,17 @@ import React from 'react' import { render, waitFor } from '@testing-library/react' import { theme, ChakraProvider } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { MockedProvider } from '@apollo/client/testing' -import { MemoryRouter, Route } from 'react-router-dom' +import { createMemoryRouter, RouterProvider } from 'react-router-dom' import { makeVar } from '@apollo/client' -import { en } from 'make-plural/plurals' - import { AdminPanel } from '../AdminPanel' - import { createCache } from '../../client' import { UserVarProvider } from '../../utilities/userState' import { rawOrgDomainListData } from '../../fixtures/orgDomainListData' import { rawOrgUserListData } from '../../fixtures/orgUserListData' -import { - PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE, - PAGINATED_ORG_DOMAINS_ADMIN_PAGE, -} from '../../graphql/queries' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) +import { PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE, PAGINATED_ORG_DOMAINS_ADMIN_PAGE } from '../../graphql/queries' +import { TourProvider } from '../../userOnboarding/contexts/TourContext' const mocks = [ { @@ -46,24 +31,37 @@ const mocks = [ }, ] +const router = createMemoryRouter( + [ + { + path: '/reset-password/:resetToken', + element: ( + + ), + }, + ], + { + initialEntries: ['/reset-password/fwsdGDFSGSDVA.gedafbedafded.bgdbsedbeagbe'], + initialIndex: 0, + }, +) + describe('', () => { it('renders both a domain list and user list', async () => { const { getByText } = render( - + - - + + - - + + diff --git a/frontend/src/admin/__tests__/AuditLogTable.test.js b/frontend/src/admin/__tests__/AuditLogTable.test.js new file mode 100644 index 0000000000..d9860efe8d --- /dev/null +++ b/frontend/src/admin/__tests__/AuditLogTable.test.js @@ -0,0 +1,443 @@ +import React from 'react' +import { theme, ChakraProvider } from '@chakra-ui/react' +import { I18nProvider } from '@lingui/react' +import { i18n } from '@lingui/core' +import { MockedProvider } from '@apollo/client/testing' +import { AuditLogTable } from '../AuditLogTable' +import { waitFor, render } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { makeVar } from '@apollo/client' +import userEvent from '@testing-library/user-event' +import { UserVarProvider } from '../../utilities/userState' +import { AUDIT_LOGS } from '../../graphql/queries' + +describe('', () => { + it('shows a table displaying activity logs from organizations', async () => { + const { queryByText } = render( + + + + + + + + + + + , + ) + + await waitFor(() => { + expect(queryByText(/Updated Properties/i)).toBeInTheDocument() + }) + }) + // pagination + + // search + + // filters + describe('with filtering options', () => { + it('filters logs by user resource, add action', async () => { + const { queryByText, getByRole, getByText } = render( + + + + + + + + + + + , + ) + + await waitFor(() => { + expect(queryByText(/Updated Properties/i)).toBeInTheDocument() + }) + const userTimestamp = getByText('2022-10-12, 17:56') + const domainTimestamp = getByText('2022-11-12, 17:56') + const orgTimestamp = getByText('2022-15-12, 17:56') + + await waitFor(() => { + expect(userTimestamp).toBeInTheDocument() + expect(domainTimestamp).toBeInTheDocument() + expect(orgTimestamp).toBeInTheDocument() + }) + + const userFilter = getByRole('button', { name: 'User' }) + const addFilter = getByRole('button', { name: 'Add' }) + userEvent.click(userFilter) + userEvent.click(addFilter) + + await waitFor(() => { + expect(domainTimestamp).not.toBeInTheDocument() + expect(orgTimestamp).not.toBeInTheDocument() + }) + }) + it('filters logs by domain resource, remove action', async () => { + const { queryByText, getByRole, getByText } = render( + + + + + + + + + + + , + ) + + await waitFor(() => { + expect(queryByText(/Updated Properties/i)).toBeInTheDocument() + }) + const userTimestamp = getByText('2022-10-12, 17:56') + const domainTimestamp = getByText('2022-11-12, 17:56') + const orgTimestamp = getByText('2022-15-12, 17:56') + + await waitFor(() => { + expect(userTimestamp).toBeInTheDocument() + expect(domainTimestamp).toBeInTheDocument() + expect(orgTimestamp).toBeInTheDocument() + }) + + const domainFilter = getByRole('button', { name: 'Domain' }) + const removeFilter = getByRole('button', { name: 'Remove' }) + userEvent.click(domainFilter) + userEvent.click(removeFilter) + + await waitFor(() => { + expect(userTimestamp).not.toBeInTheDocument() + // expect(domainTimestamp).toBeInTheDocument() + expect(orgTimestamp).not.toBeInTheDocument() + }) + }) + it('filters logs by org resource, update action', async () => { + const { queryByText, getByRole, getByText } = render( + + + + + + + + + + + , + ) + + await waitFor(() => { + expect(queryByText(/Updated Properties/i)).toBeInTheDocument() + }) + const userTimestamp = getByText('2022-10-12, 17:56') + const domainTimestamp = getByText('2022-11-12, 17:56') + const orgTimestamp = getByText('2022-15-12, 17:56') + + await waitFor(() => { + expect(userTimestamp).toBeInTheDocument() + expect(domainTimestamp).toBeInTheDocument() + expect(orgTimestamp).toBeInTheDocument() + }) + + const orgFilter = getByRole('button', { name: 'Organization' }) + const updateFilter = getByRole('button', { name: 'Update' }) + userEvent.click(orgFilter) + userEvent.click(updateFilter) + + await waitFor(() => { + expect(userTimestamp).not.toBeInTheDocument() + expect(domainTimestamp).not.toBeInTheDocument() + // expect(orgTimestamp).toBeInTheDocument() + }) + userEvent.click(orgFilter) + const clearBtn = getByRole('button', { name: 'Clear' }) + userEvent.click(clearBtn) + }) + }) +}) + +function mocks() { + return [ + { + request: { + query: AUDIT_LOGS, + variables: { + first: 50, + orderBy: { + field: 'TIMESTAMP', + direction: 'DESC', + }, + orgId: null, + search: '', + filters: { resource: [], action: [] }, + }, + }, + result: { + data: { + findAuditLogs: { + edges: [ + { + node: { + id: '3358872e-fbfa-4c73-b266-df96397f58c3', + timestamp: '2022-10-12T17:56:46.306Z', + initiatedBy: { + id: 'fb311e39-6404-4778-a4eb-9afc5a699920', + userName: 'super@user1', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'add', + target: { + resource: 'test@user.ca', + organization: { + name: 'Hello World', + }, + resourceType: 'user', + updatedProperties: [], + }, + reason: '', + }, + }, + { + node: { + id: '2e266fe9-34de-4443-a249-baad8bdbe341', + timestamp: '2022-11-12T17:56:46.306Z', + initiatedBy: { + id: '265d950a-2758-44ae-8752-b5db5aae4276', + userName: 'super@user2', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'remove', + target: { + resource: 'old.domain.ca', + organization: { + name: 'Hello World', + }, + resourceType: 'domain', + updatedProperties: [], + }, + reason: 'nonexistent', + }, + }, + { + node: { + id: '2e266fe9-34de-4443-a249-baad8bdbe3', + timestamp: '2022-15-12T17:56:46.306Z', + initiatedBy: { + id: '265d950a-2758-44ae-8752-b5db5aae4276', + userName: 'super@user3', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'update', + target: { + resource: 'my org 1', + organization: { + name: 'Hello World', + }, + updatedProperties: [ + { + name: 'cityEN', + oldValue: 'oldCity', + newValue: 'newCity', + }, + ], + resourceType: 'organization', + }, + reason: '', + }, + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'Hello World', + endCursor: 'Hello World', + }, + }, + }, + }, + }, + { + request: { + query: AUDIT_LOGS, + variables: { + first: 50, + orderBy: { + field: 'TIMESTAMP', + direction: 'DESC', + }, + orgId: null, + search: '', + filters: { resource: ['DOMAIN'], action: ['REMOVE'] }, + }, + }, + result: { + data: { + findAuditLogs: { + edges: [ + { + node: { + id: '2e266fe9-34de-4443-a249-baad8bdbe', + timestamp: '2022-11-12T17:56:46.306Z', + initiatedBy: { + id: '265d950a-2758-44ae-8752-b5db5aae4276', + userName: 'super@user2', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'remove', + target: { + resource: 'old.domain.ca', + organization: { + name: 'Hello World', + }, + resourceType: 'domain', + updatedProperties: [], + }, + reason: 'nonexistent', + }, + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'Hello World', + endCursor: 'Hello World', + }, + }, + }, + }, + }, + { + request: { + query: AUDIT_LOGS, + variables: { + first: 50, + orderBy: { + field: 'TIMESTAMP', + direction: 'DESC', + }, + orgId: null, + search: '', + filters: { resource: ['USER'], action: ['ADD'] }, + }, + }, + result: { + data: { + findAuditLogs: { + edges: [ + { + node: { + id: '3358872e-fbfa-4c73-b266-df96397f58c3', + timestamp: '2022-10-12T17:56:46.306Z', + initiatedBy: { + id: 'fb311e39-6404-4778-a4eb-9afc5a699920', + userName: 'super@user1', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'add', + target: { + resource: 'test@user.ca', + organization: { + name: 'Hello World', + }, + resourceType: 'user', + updatedProperties: [], + }, + reason: '', + }, + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'Hello World', + endCursor: 'Hello World', + }, + }, + }, + }, + }, + { + request: { + query: AUDIT_LOGS, + variables: { + first: 50, + orderBy: { + field: 'TIMESTAMP', + direction: 'DESC', + }, + orgId: null, + search: '', + filters: { resource: ['ORGANIZATION'], action: ['UPDATE'] }, + }, + }, + result: { + data: { + findAuditLogs: { + edges: [ + { + node: { + id: '2e266fe9-34de-4443-a249-baad8bdbe34', + timestamp: '2022-15-12T17:56:46.306Z', + initiatedBy: { + id: '265d950a-2758-44ae-8752-b5db5aae4276', + userName: 'super@user3', + role: 'Hello World', + organization: 'Hello World', + }, + action: 'update', + target: { + resource: 'my org 1', + organization: { + name: 'Hello World', + }, + updatedProperties: [ + { + name: 'cityEN', + oldValue: 'oldCity', + newValue: 'newCity', + }, + ], + resourceType: 'organization', + }, + reason: '', + }, + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'Hello World', + endCursor: 'Hello World', + }, + }, + }, + }, + }, + ] +} diff --git a/frontend/src/admin/__tests__/CvdEnrollmentForm.test.js b/frontend/src/admin/__tests__/CvdEnrollmentForm.test.js new file mode 100644 index 0000000000..b5e64e55e5 --- /dev/null +++ b/frontend/src/admin/__tests__/CvdEnrollmentForm.test.js @@ -0,0 +1,138 @@ +import React from 'react' +import { render, screen, fireEvent } from '@testing-library/react' +import { ChakraProvider, theme } from '@chakra-ui/react' +import { I18nProvider } from '@lingui/react' +import { i18n } from '@lingui/core' +import { CvdEnrollmentForm } from '../CvdEnrollmentForm' +import '@testing-library/jest-dom' + +describe('', () => { + const baseValues = { + cvdEnrollment: { + status: 'NOT_ENROLLED', + description: '', + maxSeverity: '', + confidentialityRequirement: '', + integrityRequirement: '', + availabilityRequirement: '', + }, + } + const handleChange = jest.fn() + + function renderForm(props) { + return render( + + + + + , + ) + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('renders the enrollment status select and info popover', () => { + renderForm({ values: baseValues, handleChange, permission: 'USER' }) + expect(screen.getAllByText(/CVD Enrollment Status/i)[0]).toBeInTheDocument() + expect(screen.getByText(/More Info/i)).toBeInTheDocument() + expect(screen.getByText(/Not Enrolled/i)).toBeInTheDocument() + }) + + it('shows Pending option for ADMIN permission', () => { + renderForm({ + values: { cvdEnrollment: { ...baseValues.cvdEnrollment, status: 'PENDING' } }, + handleChange, + permission: 'ADMIN', + }) + // Use queryByText with fallback for option + const pendingOption = screen.queryByText((content, element) => { + return element.tagName && element.tagName.toLowerCase() === 'option' && /Pending/i.test(content) + }) + expect(pendingOption).not.toBeNull() + }) + + it('shows Enrolled option for OWNER permission', () => { + renderForm({ + values: { cvdEnrollment: { ...baseValues.cvdEnrollment, status: 'ENROLLED' } }, + handleChange, + permission: 'OWNER', + }) + expect(screen.getAllByText(/Enrolled/i)[0]).toBeInTheDocument() + }) + + it('renders additional fields when status is not NOT_ENROLLED', () => { + renderForm({ + values: { + cvdEnrollment: { + ...baseValues.cvdEnrollment, + status: 'ENROLLED', + description: 'desc', + maxSeverity: 'HIGH', + confidentialityRequirement: 'LOW', + integrityRequirement: 'HIGH', + availabilityRequirement: 'LOW', + }, + }, + handleChange, + permission: 'OWNER', + }) + expect(screen.getByLabelText(/Description/i)).toBeInTheDocument() + expect(screen.getByLabelText(/Max Severity/i)).toBeInTheDocument() + expect(screen.getByLabelText(/Confidentiality Requirement/i)).toBeInTheDocument() + expect(screen.getByLabelText(/Integrity Requirement/i)).toBeInTheDocument() + expect(screen.getByLabelText(/Availability Requirement/i)).toBeInTheDocument() + }) + + it('calls handleChange when status is changed', () => { + renderForm({ values: baseValues, handleChange, permission: 'ADMIN' }) + // Use getByRole to select by combobox and name + const select = screen.getByRole('combobox', { name: /CVD Enrollment Status/i }) + fireEvent.change(select, { + target: { value: 'PENDING' }, + }) + expect(handleChange).toHaveBeenCalled() + }) + + it('calls handleChange for description input', () => { + renderForm({ + values: { + cvdEnrollment: { ...baseValues.cvdEnrollment, status: 'ENROLLED' }, + }, + handleChange, + permission: 'OWNER', + }) + fireEvent.change(screen.getByLabelText(/Description/i), { + target: { value: 'Test description' }, + }) + expect(handleChange).toHaveBeenCalled() + }) + + it('handles empty values for select fields', () => { + renderForm({ + values: { + cvdEnrollment: { + status: 'ENROLLED', + description: '', + maxSeverity: '', + confidentialityRequirement: '', + integrityRequirement: '', + availabilityRequirement: '', + }, + }, + handleChange, + permission: 'OWNER', + }) + expect(screen.getByLabelText(/Max Severity/i).value).toBe('') + expect(screen.getByLabelText(/Confidentiality Requirement/i).value).toBe('') + expect(screen.getByLabelText(/Integrity Requirement/i).value).toBe('') + expect(screen.getByLabelText(/Availability Requirement/i).value).toBe('') + }) + + it('does not render additional fields when status is NOT_ENROLLED', () => { + renderForm({ values: baseValues, handleChange, permission: 'USER' }) + expect(screen.queryByLabelText(/Description/i)).not.toBeInTheDocument() + expect(screen.queryByLabelText(/Max Severity/i)).not.toBeInTheDocument() + }) +}) diff --git a/frontend/src/admin/__tests__/DomainTagsList.test.js b/frontend/src/admin/__tests__/DomainTagsList.test.js new file mode 100644 index 0000000000..2b980e044c --- /dev/null +++ b/frontend/src/admin/__tests__/DomainTagsList.test.js @@ -0,0 +1,169 @@ +import React from 'react' +import { ChakraProvider, theme } from '@chakra-ui/react' +import { MemoryRouter } from 'react-router-dom' +import { I18nProvider } from '@lingui/react' +import { i18n } from '@lingui/core' +import { makeVar } from '@apollo/client' +import { render, screen, fireEvent, within } from '@testing-library/react' +import { MockedProvider } from '@apollo/client/testing' +import { DomainTagsList } from '../DomainTagsList' +import { DOMAIN_TAGS } from '../../graphql/queries' +import { CREATE_TAG, UPDATE_TAG } from '../../graphql/mutations' +import { UserVarProvider } from '../../utilities/userState' +import '@testing-library/jest-dom' + +jest.mock('@chakra-ui/react', () => { + const actual = jest.requireActual('@chakra-ui/react') + return { ...actual, useToast: () => jest.fn() } +}) + +jest.mock('@lingui/macro', () => ({ + t: (str) => str, + Trans: ({ children }) => children, +})) + +jest.mock('../../utilities/fieldRequirements', () => { + const Yup = require('yup') + return { + getRequirement: () => ({ required: true }), + schemaToValidation: () => + Yup.object().shape({ + labelEn: Yup.string().required(), + labelFr: Yup.string(), + isVisible: Yup.boolean(), + ownership: Yup.string(), + }), + } +}) + +jest.mock('../../components/LoadingMessage', () => ({ + LoadingMessage: () =>
Loading...
, +})) + +jest.mock('../../components/ErrorFallbackMessage', () => ({ + ErrorFallbackMessage: () =>
Error!
, +})) + +const mockTags = [ + { + tagId: '1', + label: 'Important', + description: 'Critical tag', + isVisible: true, + ownership: 'GLOBAL', + organizations: [], + }, + { + tagId: '2', + label: 'Archived', + description: 'No longer used', + isVisible: false, // so visibility icon appears + ownership: 'ORG', + organizations: [], + }, +] + +const mocks = [ + { + request: { query: DOMAIN_TAGS, variables: { orgId: undefined, isVisible: true } }, + result: { data: { findAllTags: mockTags } }, + }, + { + request: { + query: CREATE_TAG, + variables: { + labelEn: '', + labelFr: '', + descriptionEn: '', + descriptionFr: '', + isVisible: true, + ownership: '', + }, + }, + result: { + data: { createTag: { result: { __typename: 'Tag', tag: 'NewTag' } } }, + }, + }, + { + request: { + query: UPDATE_TAG, + variables: { tagId: '1', labelEn: 'Updated' }, + }, + result: { + data: { updateTag: { result: { __typename: 'Tag', tagId: '1' } } }, + }, + }, +] + +const noTagsMocks = [ + { + request: { query: DOMAIN_TAGS, variables: { orgId: undefined, isVisible: true } }, + result: { data: { findAllTags: [] } }, + }, +] + +function renderWithProviders(ui, { mocksOverride = [] } = {}) { + return render( + + + + + {ui} + + + + , + ) +} + +describe('DomainTagsList', () => { + it('renders loading state', () => { + renderWithProviders(, { mocksOverride: [] }) + expect(screen.getByTestId('loading')).toBeInTheDocument() + }) + + it('renders error state', async () => { + const errorMock = [ + { + request: { query: DOMAIN_TAGS, variables: { orgId: undefined, isVisible: true } }, + error: new Error('GraphQL error: Failed to fetch'), + }, + ] + renderWithProviders(, { mocksOverride: errorMock }) + expect(await screen.findByTestId('error')).toBeInTheDocument() + }) + + it('renders tags and allows toggling edit forms', async () => { + renderWithProviders() + expect(await screen.findByText(/IMPORTANT/i)).toBeInTheDocument() + expect(screen.getByText(/ARCHIVED/i)).toBeInTheDocument() + + const editButtons = screen.getAllByRole('button', { name: /edit tag/i }) + fireEvent.click(editButtons[0]) + expect(await screen.findAllByText(/visible/i)).not.toHaveLength(0) + }) + + it('opens and closes create form', async () => { + renderWithProviders() + const addTagBtn = await screen.findByRole('button', { name: /Add Tag/i }) + fireEvent.click(addTagBtn) + + const confirmBtn = await screen.findByRole('button', { name: /Confirm/i }) + const closeBtn = screen.getByRole('button', { name: /Close/i }) + fireEvent.click(closeBtn) + + expect(confirmBtn).not.toBeVisible() + }) + + it('shows "No Tags" when none returned', async () => { + renderWithProviders(, { mocksOverride: noTagsMocks }) + expect(await screen.findByText(/No Tags/i)).toBeInTheDocument() + }) + + it('toggles visibility icon for invisible tags', async () => { + renderWithProviders() + const archivedTag = await screen.findByText(/ARCHIVED/i) + const icon = within(archivedTag.closest('div')).getByLabelText('tag-invisible') + expect(icon).toBeInTheDocument() + }) +}) diff --git a/frontend/src/admin/__tests__/DomainUpdateList.test.js b/frontend/src/admin/__tests__/DomainUpdateList.test.js new file mode 100644 index 0000000000..17f3665127 --- /dev/null +++ b/frontend/src/admin/__tests__/DomainUpdateList.test.js @@ -0,0 +1,394 @@ +import React from 'react' +import { render, fireEvent, waitFor } from '@testing-library/react' +import { ChakraProvider, theme } from '@chakra-ui/react' +import { I18nProvider } from '@lingui/react' +import { i18n } from '@lingui/core' +import { MockedProvider } from '@apollo/client/testing' +import { DomainUpdateList } from '../DomainUpdateList' +import { UPDATE_DOMAINS_BY_DOMAIN_IDS, UPDATE_DOMAINS_BY_FILTERS } from '../../graphql/mutations' + +const domains = [ + { id: '1', domain: 'example.com', tags: ['tag1'] }, + { id: '2', domain: 'test.com', tags: ['tag2'] }, +] + +const availableTags = [ + { label: 'Tag 1', tagId: 'tag1' }, + { label: 'Tag 2', tagId: 'tag2' }, +] + +const filters = [] +const orgId = 'org-1' +const search = '' +const domainCount = 2 + +const mocks = [ + { + request: { + query: UPDATE_DOMAINS_BY_DOMAIN_IDS, + variables: { domainIds: ['1'], tags: ['tag1'], orgId }, + }, + result: { + data: { + updateDomainsByDomainIds: { + result: { + __typename: 'DomainBulkResult', + status: 'Success', + }, + }, + }, + }, + }, + // Added mock for select-all (multiple domainIds) + { + request: { + query: UPDATE_DOMAINS_BY_DOMAIN_IDS, + variables: { domainIds: ['1', '2'], tags: ['tag1'], orgId }, + }, + result: { + data: { + updateDomainsByDomainIds: { + result: { + __typename: 'DomainBulkResult', + status: 'Success', + }, + }, + }, + }, + }, + { + request: { + query: UPDATE_DOMAINS_BY_FILTERS, + variables: { filters, search, tags: ['tag1'], orgId }, + }, + result: { + data: { + updateDomainsByFilters: { + result: { + __typename: 'DomainBulkResult', + status: 'Success', + }, + }, + }, + }, + }, +] + +describe('', () => { + it('renders domain rows and select all', () => { + const { getByText, getByLabelText } = render( + + + + + + + , + ) + expect(getByText('example.com')).toBeInTheDocument() + expect(getByText('test.com')).toBeInTheDocument() + expect(getByLabelText(/Select All/i)).toBeInTheDocument() + }) + + it('selects a domain and opens tag drawer', async () => { + const { getAllByRole, getByText, getByRole } = render( + + + + + + + , + ) + const checkboxes = getAllByRole('checkbox') + fireEvent.click(checkboxes[1]) // select first domain + const tagBtn = getByRole('button', { name: /Tag Assets/i }) + fireEvent.click(tagBtn) + await waitFor(() => expect(getByText(/Apply Tags/i)).toBeInTheDocument()) + }) + + it('applies tags to selected domains', async () => { + const resetToFirstPage = jest.fn() + const { getAllByRole, getByRole, getByText, getByLabelText, _getByRole } = render( + + + + + + + , + ) + const checkboxes = getAllByRole('checkbox') + fireEvent.click(checkboxes[1]) // select first domain + fireEvent.click(getByRole('button', { name: /Tag Assets/i })) + await waitFor(() => getByText(/Apply Tags/i)) + fireEvent.click(getByLabelText('TAG 1')) + fireEvent.click(getByRole('button', { name: /Apply/i })) + await waitFor(() => getByText(/Are you sure\?/i)) + fireEvent.click(getByRole('button', { name: /Yes, Apply/i })) + await waitFor(() => getByText(/Domains updated/i)) + }) + + it('applies tags to all filtered domains when select all is checked', async () => { + const resetToFirstPage = jest.fn() + const { getAllByRole, getByRole, getByText, getByLabelText } = render( + + + + + + + , + ) + const selectAll = getAllByRole('checkbox')[0] + fireEvent.click(selectAll) + fireEvent.click(getByRole('button', { name: /Tag Assets/i })) + await waitFor(() => getByText(/Apply Tags/i)) + fireEvent.click(getByLabelText('TAG 1')) + fireEvent.click(getByRole('button', { name: /Apply/i })) + await waitFor(() => getByText(/Are you sure\?/i)) + fireEvent.click(getByRole('button', { name: /Yes, Apply/i })) + await waitFor(() => { + expect(document.body.textContent).toMatch(/Domains updated/i) + }) + }) + + it('shows error toast on mutation error', async () => { + const errorMocks = [ + { + request: { + query: UPDATE_DOMAINS_BY_DOMAIN_IDS, + variables: { domainIds: ['1'], tags: ['tag1'], orgId }, + }, + error: new Error('Test error'), + }, + ] + const { getAllByRole, getByText, getByRole, getByLabelText } = render( + + + + + + + , + ) + const checkboxes = getAllByRole('checkbox') + fireEvent.click(checkboxes[1]) + fireEvent.click(getByRole('button', { name: 'Tag Assets' })) + await waitFor(() => getByText(/Apply Tags/i)) + fireEvent.click(getByLabelText('TAG 1')) + fireEvent.click(getByRole('button', { name: 'Apply' })) + await waitFor(() => getByText(/Are you sure\?/i)) + fireEvent.click(getByRole('button', { name: 'Yes, Apply' })) + await waitFor(() => getByText(/An error occurred/i)) + }) + + it('selects multiple domains across pages and preserves selection', () => { + // Simulate two pages of domains + const page1 = [ + { id: '1', domain: 'example.com', tags: ['tag1'] }, + { id: '2', domain: 'test.com', tags: ['tag2'] }, + ] + const page2 = [ + { id: '3', domain: 'foo.com', tags: ['tag1'] }, + { id: '4', domain: 'bar.com', tags: ['tag2'] }, + ] + // Render page 1 + const { getAllByRole, rerender, getByText, getAllByText } = render( + + + + + + + , + ) + // Select first domain on page 1 + const checkboxes1 = getAllByRole('checkbox') + fireEvent.click(checkboxes1[1]) + expect(getByText(/1 selected on this page/i)).toBeInTheDocument() + // Go to page 2 + rerender( + + + + + + + , + ) + // Select all on page 2 + const checkboxes2 = getAllByRole('checkbox') + fireEvent.click(checkboxes2[0]) // select all on page 2 + // Only match the visible banner, not the aria-live region + const visibleBanner = getAllByText(/All 2 domains on this page are selected/i).find( + (el) => el.tagName.toLowerCase() === 'p', + ) + expect(visibleBanner).toBeInTheDocument() + // Go back to page 1, both selections should persist + rerender( + + + + + + + , + ) + // The first domain should still be selected + expect(getByText(/1 selected on this page/i)).toBeInTheDocument() + }) + + it('shows indeterminate state when some but not all on page are selected', () => { + const { getAllByRole, getByText } = render( + + + + + + + , + ) + // Select only one domain + const checkboxes = getAllByRole('checkbox') + fireEvent.click(checkboxes[1]) + // The select all checkbox should be indeterminate + expect(checkboxes[0].indeterminate).toBe(true) + expect(getByText(/1 selected on this page/i)).toBeInTheDocument() + }) + + it('select all on page only adds those domains to selection, not clearing others', () => { + // page 1 + const page1 = [ + { id: '1', domain: 'example.com', tags: ['tag1'] }, + { id: '2', domain: 'test.com', tags: ['tag2'] }, + ] + // page 2 + const page2 = [ + { id: '3', domain: 'foo.com', tags: ['tag1'] }, + { id: '4', domain: 'bar.com', tags: ['tag2'] }, + ] + const { getAllByRole, rerender, getByText } = render( + + + + + + + , + ) + // Select first domain on page 1 + const checkboxes1 = getAllByRole('checkbox') + fireEvent.click(checkboxes1[1]) + // Go to page 2 and select all + rerender( + + + + + + + , + ) + const checkboxes2 = getAllByRole('checkbox') + fireEvent.click(checkboxes2[0]) // select all on page 2 + // Go back to page 1, both selections should persist + rerender( + + + + + + + , + ) + // Both domains on page 1 should be selectable, and the first should still be selected + expect(getByText(/1 selected on this page/i)).toBeInTheDocument() + }) +}) diff --git a/frontend/src/admin/__tests__/OrganizationInformation.test.js b/frontend/src/admin/__tests__/OrganizationInformation.test.js index 6d86ab007b..701dc492f1 100644 --- a/frontend/src/admin/__tests__/OrganizationInformation.test.js +++ b/frontend/src/admin/__tests__/OrganizationInformation.test.js @@ -1,10 +1,10 @@ import React from 'react' import { ChakraProvider, theme } from '@chakra-ui/react' import { MemoryRouter } from 'react-router-dom' -import { render, waitFor } from '@testing-library/react' +import { act, render, waitFor } from '@testing-library/react' import { MockedProvider } from '@apollo/client/testing' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import userEvent from '@testing-library/user-event' import { makeVar } from '@apollo/client' import { en } from 'make-plural/plurals' @@ -14,20 +14,11 @@ import { OrganizationInformation } from '../OrganizationInformation' import { createCache } from '../../client' import { UserVarProvider } from '../../utilities/userState' import { ORGANIZATION_INFORMATION } from '../../graphql/queries' -import { - REMOVE_ORGANIZATION, - UPDATE_ORGANIZATION, -} from '../../graphql/mutations' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) +import { REMOVE_ORGANIZATION, UPDATE_ORGANIZATION } from '../../graphql/mutations' + +i18n.loadLocaleData('en', { plurals: en }) +i18n.load('en', { en: {} }) +i18n.activate('en') const mocks = [ { @@ -48,6 +39,7 @@ const mocks = [ province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -79,8 +71,8 @@ const mocks = [ query: UPDATE_ORGANIZATION, variables: { id: 'org-id', - acronymEN: 'NEWACREN', - acronymFR: 'NEWACRFR', + nameEN: 'NEW ACREN', + nameFR: 'NEW ACRFR', countryEN: 'Canada', }, }, @@ -89,7 +81,7 @@ const mocks = [ updateOrganization: { result: { id: 'org-id', - acronym: 'NEWACREN', + acronym: 'NEW ACREN', name: 'Org Name', slug: 'org-name', zone: 'org zone', @@ -98,6 +90,7 @@ const mocks = [ province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -122,10 +115,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -135,7 +125,7 @@ describe('', () => { await findByText(/Org Name/) - expect(queryByText(/org sector/)).toBeInTheDocument() + expect(queryByText(/org country/)).toBeInTheDocument() }) it('organization editing area is hidden', async () => { @@ -151,10 +141,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -164,12 +151,10 @@ describe('', () => { await findByText(/Org Name/) - expect( - queryByText(/Blank fields will not be included/), - ).not.toBeVisible() + expect(queryByText(/Blank fields will not be included/)).not.toBeVisible() }) - it('org editing error can open and close', async () => { + it('org editing error can be opened', async () => { const mocks = [ { request: { @@ -189,6 +174,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -229,10 +215,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -241,9 +224,7 @@ describe('', () => { ) await findByText(/Org Name/) - expect( - queryByText(/Blank fields will not be included/), - ).not.toBeVisible() + expect(queryByText(/Blank fields will not be included/)).not.toBeVisible() const editOrgButton = await findByRole('button', { name: /Edit Organization/, @@ -255,19 +236,7 @@ describe('', () => { userEvent.click(editOrgButton) // ensure editing area is open - await waitFor(() => - expect(getByText(/Blank fields will not be included/)).toBeVisible(), - ) - - // close editing area - userEvent.click(editOrgButton) - - // ensure editing area is closed - await waitFor(() => - expect( - queryByText(/Blank fields will not be included/), - ).not.toBeVisible(), - ) + await waitFor(() => expect(getByText(/Blank fields will not be included/)).toBeVisible()) }) it('can remove the organization', async () => { @@ -290,6 +259,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -330,10 +300,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -359,19 +326,13 @@ describe('', () => { await waitFor(() => expect(removeOrgInput).toBeVisible()) - expect( - getByText( - /Are you sure you want to permanently remove the organization "Org Name"?/, - ), - ).toBeVisible() + expect(getByText(/Are you sure you want to permanently remove the organization "Org Name"?/)).toBeVisible() userEvent.type(removeOrgInput, 'Org Name') userEvent.click(confirmOrganizationRemovalButton) - const successfullyRemoveToastText = await findByText( - /You have successfully removed Org Name/, - ) + const successfullyRemoveToastText = await findByText(/You have successfully removed Org Name/) await waitFor(() => expect(successfullyRemoveToastText).toBeVisible()) }) @@ -396,6 +357,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -424,8 +386,8 @@ describe('', () => { query: UPDATE_ORGANIZATION, variables: { id: 'org-id', - acronymEN: 'NEWACREN', - acronymFR: 'NEWACRFR', + nameEN: 'NEW ACREN', + nameFR: 'NEWA CRFR', countryEN: 'Canada', }, }, @@ -434,7 +396,7 @@ describe('', () => { updateOrganization: { result: { id: 'org-id', - acronym: 'NEWACREN', + acronym: 'NEW ACREN', name: 'Org Name', slug: 'org-name', zone: 'org zone', @@ -443,6 +405,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -463,10 +426,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -492,19 +452,13 @@ describe('', () => { await waitFor(() => expect(removeOrgInput).toBeVisible()) - expect( - getByText( - /Are you sure you want to permanently remove the organization "Org Name"?/, - ), - ).toBeVisible() + expect(getByText(/Are you sure you want to permanently remove the organization "Org Name"?/)).toBeVisible() userEvent.type(removeOrgInput, 'Org Name') userEvent.click(confirmOrganizationRemovalButton) - const successfullyRemoveToastText = await findByText( - /Could not remove this organization/, - ) + const successfullyRemoveToastText = await findByText(/Could not remove this organization/) await waitFor(() => expect(successfullyRemoveToastText).toBeVisible()) }) @@ -522,10 +476,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -550,11 +501,7 @@ describe('', () => { await waitFor(() => expect(removeOrgInput).toBeVisible()) - expect( - getByText( - /Are you sure you want to permanently remove the organization "Org Name"?/, - ), - ).toBeVisible() + expect(getByText(/Are you sure you want to permanently remove the organization "Org Name"?/)).toBeVisible() userEvent.click(confirmOrganizationRemovalButton) @@ -576,10 +523,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -595,17 +539,13 @@ describe('', () => { userEvent.click(editOrgButton) - await waitFor(() => - expect( - getByText(/Blank fields will not be included/), - ).toBeVisible(), - ) + await waitFor(() => expect(getByText(/Blank fields will not be included/)).toBeVisible()) - const acronymENInput = await findByRole('textbox', { - name: 'Acronym (EN)', + const nameENInput = await findByRole('textbox', { + name: 'Name (EN)', }) - const acronymFRInput = getByRole('textbox', { - name: 'Acronym (FR)', + const nameFRInput = getByRole('textbox', { + name: 'Name (FR)', }) const countryENInput = getByRole('textbox', { name: 'Country (EN)', @@ -614,15 +554,15 @@ describe('', () => { name: 'Confirm', }) - userEvent.type(acronymENInput, 'NEWACREN') - userEvent.type(acronymFRInput, 'NEWACRFR') + userEvent.type(nameENInput, 'NEW ACREN') + userEvent.type(nameFRInput, 'NEW ACRFR') userEvent.type(countryENInput, 'Canada') - userEvent.click(confrimOrganizationUpdateButton) + await act(() => { + userEvent.click(confrimOrganizationUpdateButton) + }) - const successfulUpdateToastText = await findByText( - /You have successfully updated Org Name/, - ) + const successfulUpdateToastText = await findByText(/You have successfully updated Org Name/) await waitFor(() => expect(successfulUpdateToastText).toBeVisible()) @@ -651,6 +591,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -682,8 +623,8 @@ describe('', () => { query: UPDATE_ORGANIZATION, variables: { id: 'org-id', - acronymEN: 'NEWACREN', - acronymFR: 'NEWACRFR', + nameEN: 'NEW ACREN', + nameFR: 'NEW ACRFR', countryEN: 'Canada', }, }, @@ -713,10 +654,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -732,17 +670,13 @@ describe('', () => { userEvent.click(editOrgButton) - await waitFor(() => - expect( - getByText(/Blank fields will not be included/), - ).toBeVisible(), - ) + await waitFor(() => expect(getByText(/Blank fields will not be included/)).toBeVisible()) - const acronymENInput = await findByRole('textbox', { - name: 'Acronym (EN)', + const nameENInput = await findByRole('textbox', { + name: 'Name (EN)', }) - const acronymFRInput = getByRole('textbox', { - name: 'Acronym (FR)', + const nameFRInput = getByRole('textbox', { + name: 'Name (FR)', }) const countryENInput = getByRole('textbox', { name: 'Country (EN)', @@ -751,15 +685,13 @@ describe('', () => { name: 'Confirm', }) - userEvent.type(acronymENInput, 'NEWACREN') - userEvent.type(acronymFRInput, 'NEWACRFR') + userEvent.type(nameENInput, 'NEW ACREN') + userEvent.type(nameFRInput, 'NEW ACRFR') userEvent.type(countryENInput, 'Canada') userEvent.click(confrimOrganizationUpdateButton) - const successfulUpdateToastText = await findByText( - /Unable to update this organization error/, - ) + const successfulUpdateToastText = await findByText(/Unable to update this organization error/) await waitFor(() => expect(successfulUpdateToastText).toBeVisible()) @@ -782,10 +714,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -801,11 +730,7 @@ describe('', () => { userEvent.click(editOrgButton) - await waitFor(() => - expect( - getByText(/Blank fields will not be included/), - ).toBeVisible(), - ) + await waitFor(() => expect(getByText(/Blank fields will not be included/)).toBeVisible()) const confrimOrganizationUpdateButton = getByRole('button', { name: 'Confirm', @@ -813,9 +738,7 @@ describe('', () => { userEvent.click(confrimOrganizationUpdateButton) - const noValuesSuppliedToastText = await findByText( - /No values were supplied/, - ) + const noValuesSuppliedToastText = await findByText(/No values were supplied/) await waitFor(() => expect(noValuesSuppliedToastText).toBeVisible()) }) diff --git a/frontend/src/admin/__tests__/SuperAdminUserList.test.js b/frontend/src/admin/__tests__/SuperAdminUserList.test.js new file mode 100644 index 0000000000..75a2dccb48 --- /dev/null +++ b/frontend/src/admin/__tests__/SuperAdminUserList.test.js @@ -0,0 +1,373 @@ +import React from 'react' +import { fireEvent, render, waitFor } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { theme, ChakraProvider } from '@chakra-ui/react' +import { I18nProvider } from '@lingui/react' +import { MockedProvider } from '@apollo/client/testing' +import { i18n } from '@lingui/core' +import { makeVar } from '@apollo/client' +import { SuperAdminUserList } from '../SuperAdminUserList' +import { UserVarProvider } from '../../utilities/userState' +import { createCache } from '../../client' +import { FIND_MY_USERS } from '../../graphql/queries' +import { UPDATE_USER_ROLE, REMOVE_USER_FROM_ORG } from '../../graphql/mutations' + +const saUserListMockData = { + data: { + findMyUsers: { + edges: [ + { + cursor: 'Hello World', + node: { + id: '39b345c9-55c6-4d0d-83e3-c517141beabe', + userName: 'Raegan.Ritchie50@yahoo.com', + displayName: 'Dr. Kirk Orn', + emailValidated: false, + affiliations: { + totalCount: 2, + edges: [ + { + node: { + permission: 'USER', + organization: { + id: '5585133c-1ac8-4813-81bb-448a0e093c01', + acronym: 'CICS', + name: 'Kreiger - Schamberger', + slug: 'Kreiger---Schamberger', + verified: true, + }, + }, + }, + { + node: { + permission: 'ADMIN', + organization: { + id: '1c28618a-b792-4953-9f04-1522a98470ab', + acronym: 'FCAC', + name: 'Leannon, Sporer and Langworth', + slug: 'Leannon-Sporer-and-Langworth', + verified: false, + }, + }, + }, + ], + }, + }, + }, + { + cursor: 'Hello World', + node: { + id: '760a4af2-0b33-4567-8b3b-57e0f84cb570', + userName: 'Kacey.Witting43@hotmail.com', + displayName: 'Jonathan Kassulke', + emailValidated: false, + affiliations: { + totalCount: 2, + edges: [ + { + node: { + permission: 'ADMIN', + organization: { + id: 'e5c355ae-5ec0-47a9-9156-2889ac51ca75', + acronym: 'CICS', + name: 'Funk Group', + slug: 'Funk-Group', + verified: false, + }, + }, + }, + { + node: { + permission: 'ADMIN', + organization: { + id: '080fde14-f99c-4739-8058-099dfd495d0d', + acronym: 'CRA', + name: 'Cole, Lockman and Pagac', + slug: 'Cole-Lockman-and-Pagac', + verified: false, + }, + }, + }, + ], + }, + }, + }, + { + cursor: 'Hello World', + node: { + id: 'hb47h74edrbh48d4658f4n9', + userName: 'test.user@test.ca', + displayName: 'Arny Schwartz', + emailValidated: true, + affiliations: { + totalCount: 0, + edges: [], + }, + }, + }, + ], + pageInfo: { + hasNextPage: false, + endCursor: 'Hello World', + hasPreviousPage: true, + startCursor: 'Hello World', + __typename: 'PageInfo', + }, + }, + }, +} + +const successMocks = [ + { + request: { + query: FIND_MY_USERS, + variables: { + first: 50, + orderBy: { field: 'USER_USERNAME', direction: 'ASC' }, + search: '', + }, + }, + result: saUserListMockData, + }, + { + request: { + query: UPDATE_USER_ROLE, + variables: { + userName: saUserListMockData.data.findMyUsers.edges[0].node.userName, + orgId: saUserListMockData.data.findMyUsers.edges[0].node.affiliations.edges[0].node.organization.id, + role: 'ADMIN', + }, + }, + result: { + data: { + updateUserRole: { + result: { + status: 'Hello World', + __typename: 'UpdateUserRoleResult', + }, + __typename: 'UpdateUserRolePayload', + }, + }, + }, + }, + { + request: { + query: REMOVE_USER_FROM_ORG, + variables: { + userId: saUserListMockData.data.findMyUsers.edges[0].node.id, + orgId: saUserListMockData.data.findMyUsers.edges[0].node.affiliations.edges[0].node.organization.id, + }, + }, + result: { + data: { + removeUserFromOrg: { + result: { + status: 'Hello World', + user: { + userName: 'Ethelyn_Senger86@hotmail.com', + __typename: 'SharedUser', + }, + __typename: 'RemoveUserFromOrgResult', + }, + __typename: 'RemoveUserFromOrgPayload', + }, + }, + }, + }, +] + +describe('', () => { + it('successfully renders with mocked data', async () => { + const { queryByText } = render( + + + + + + + + + + + , + ) + // wait for query to load + await waitFor(() => expect(queryByText(/search/i)).toBeInTheDocument()) + // get user data + await waitFor(() => expect(queryByText(/Jonathan Kassulke/i)).toBeInTheDocument()) + }) + describe('individual user cards', () => { + it('are clickable', async () => { + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + // wait for query to load + await waitFor(() => expect(queryByText(/search/i)).toBeInTheDocument()) + // get user data + await waitFor(() => expect(queryByText(/Raegan.Ritchie50@yahoo.com/i)).toBeInTheDocument()) + const userCard1 = getByRole('button', { + name: /Raegan.Ritchie50@yahoo.com/i, + }) + fireEvent.click(userCard1) + }) + describe('when clicked', () => { + describe('when user has affiliations', () => { + it('lists cards for each org', async () => { + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + // wait for query to load + await waitFor(() => expect(queryByText(/search/i)).toBeInTheDocument()) + // get user data + await waitFor(() => expect(queryByText(/Raegan.Ritchie50@yahoo.com/i)).toBeInTheDocument()) + const userCard1 = getByRole('button', { + name: /Raegan.Ritchie50@yahoo.com/i, + }) + fireEvent.click(userCard1) + + await waitFor(() => expect(queryByText(/Kreiger - Schamberger/i)).toBeInTheDocument()) + }) + + describe('admin abilities', () => { + it("edit user's role in org", async () => { + const { queryByText, getByRole, queryAllByText } = render( + + + + + + + + + + + , + ) + // wait for query to load + await waitFor(() => expect(queryByText(/search/i)).toBeInTheDocument()) + // get user data + await waitFor(() => expect(queryByText(/Raegan.Ritchie50@yahoo.com/i)).toBeInTheDocument()) + const userCard1 = getByRole('button', { + name: /Raegan.Ritchie50@yahoo.com/i, + }) + fireEvent.click(userCard1) + await waitFor(() => expect(queryByText(/Kreiger - Schamberger/i)).toBeInTheDocument()) + const editBtn = getByRole('button', { + name: 'Edit Raegan.Ritchie50@yahoo.com in Kreiger - Schamberger', + }) + fireEvent.click(editBtn) + await waitFor(() => expect(queryAllByText(/Edit User/i)[0]).toBeInTheDocument()) + }) + it('remove user from org', async () => { + const { queryByText, getByRole, queryAllByText } = render( + + + + + + + + + + + , + ) + // wait for query to load + await waitFor(() => expect(queryByText(/search/i)).toBeInTheDocument()) + // get user data + await waitFor(() => expect(queryByText(/Raegan.Ritchie50@yahoo.com/i)).toBeInTheDocument()) + const userCard1 = getByRole('button', { + name: /Raegan.Ritchie50@yahoo.com/i, + }) + fireEvent.click(userCard1) + await waitFor(() => expect(queryByText(/Kreiger - Schamberger/i)).toBeInTheDocument()) + const removeBtn = getByRole('button', { + name: 'Remove Raegan.Ritchie50@yahoo.com from Kreiger - Schamberger', + }) + fireEvent.click(removeBtn) + await waitFor(() => expect(queryAllByText(/Remove User/i))) + }) + }) + }) + describe('when user has no affilitions', () => { + it('displays appropriate message', async () => { + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + // wait for query to load + await waitFor(() => expect(queryByText(/search/i)).toBeInTheDocument()) + // get user data + await waitFor(() => expect(queryByText(/Raegan.Ritchie50@yahoo.com/i)).toBeInTheDocument()) + const userCard1 = getByRole('button', { + name: /Raegan.Ritchie50@yahoo.com/i, + }) + fireEvent.click(userCard1) + + await waitFor(() => + expect(queryByText(/This user is not affiliated with any organizations/i)).toBeInTheDocument(), + ) + }) + }) + }) + }) +}) diff --git a/frontend/src/admin/__tests__/TagForm.test.js b/frontend/src/admin/__tests__/TagForm.test.js new file mode 100644 index 0000000000..cc907b7c54 --- /dev/null +++ b/frontend/src/admin/__tests__/TagForm.test.js @@ -0,0 +1,175 @@ +import React from 'react' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { MockedProvider } from '@apollo/client/testing' +import { ChakraProvider, theme } from '@chakra-ui/react' +import { I18nProvider } from '@lingui/react' +import { i18n } from '@lingui/core' +import { TagForm } from '../TagForm' +import { CREATE_TAG, UPDATE_TAG } from '../../graphql/mutations' +import '@testing-library/jest-dom' + +jest.mock('@chakra-ui/react', () => { + const actual = jest.requireActual('@chakra-ui/react') + return { + ...actual, + useToast: () => jest.fn(), + } +}) + +jest.mock('@lingui/macro', () => ({ + t: (str) => str, + Trans: ({ children }) => children, +})) + +jest.mock('../../utilities/fieldRequirements', () => { + const Yup = require('yup') + return { + getRequirement: () => ({ required: true }), + schemaToValidation: () => + Yup.object().shape({ + labelEn: Yup.string().required(), + labelFr: Yup.string(), + isVisible: Yup.boolean(), + ownership: Yup.string(), + }), + } +}) + +describe('', () => { + const setTagFormState = jest.fn() + const defaultProps = { + visible: true, + ownership: '', + setTagFormState, + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('renders create form and allows input', async () => { + render( + + + + + + + , + ) + expect(screen.getByLabelText(/Label \(EN\)/i)).toBeInTheDocument() + fireEvent.change(screen.getByLabelText(/Label \(EN\)/i), { target: { value: 'TestTag' } }) + expect(screen.getByLabelText(/Label \(EN\)/i)).toHaveValue('TestTag') + }) + + it('renders update form and allows input', async () => { + render( + + + + + + + , + ) + expect(screen.getByLabelText(/Label \(EN\)/i)).toBeInTheDocument() + fireEvent.change(screen.getByLabelText(/Label \(EN\)/i), { target: { value: 'UpdatedTag' } }) + expect(screen.getByLabelText(/Label \(EN\)/i)).toHaveValue('UpdatedTag') + }) + + it('calls setTagFormState on Close (create)', async () => { + render( + + + + + + + , + ) + fireEvent.click(screen.getByText(/Close/i)) + expect(setTagFormState).toHaveBeenCalledWith(expect.any(Function)) + }) + + it('calls setTagFormState on Close (update)', async () => { + render( + + + + + + + , + ) + fireEvent.click(screen.getByText(/Close/i)) + expect(setTagFormState).toHaveBeenCalledWith(expect.any(Function)) + }) + + it('submits create form and disables button while loading', async () => { + const mocks = [ + { + request: { + query: CREATE_TAG, + variables: { + labelEn: 'TestTag', + labelFr: '', + descriptionEn: '', + descriptionFr: '', + isVisible: true, + ownership: '', + }, + }, + result: { + data: { + createTag: { + result: { __typename: 'Tag', tag: 'TestTag' }, + }, + }, + }, + }, + ] + render( + + + + + + + , + ) + fireEvent.change(screen.getByLabelText(/Label \(EN\)/i), { target: { value: 'TestTag' } }) + fireEvent.click(screen.getByText(/Confirm/i)) + await waitFor(() => expect(setTagFormState).toHaveBeenCalled()) + }) + + it('calls handleReset on Clear', async () => { + render( + + + + + + + , + ) + const clearBtn = screen.getByText(/Clear/i) + fireEvent.click(clearBtn) + // No error = pass, Formik handles reset + }) + + it('shows validation error if required field is empty', async () => { + render( + + + + + + + , + ) + fireEvent.click(screen.getByText(/Confirm/i)) + await waitFor(() => { + expect(screen.getByLabelText(/Label \(EN\)/i)).toBeInvalid() + }) + }) +}) diff --git a/frontend/src/admin/__tests__/UserList.test.js b/frontend/src/admin/__tests__/UserList.test.js index d8741b9013..418457bc2e 100644 --- a/frontend/src/admin/__tests__/UserList.test.js +++ b/frontend/src/admin/__tests__/UserList.test.js @@ -4,44 +4,39 @@ import { MemoryRouter } from 'react-router-dom' import { theme, ChakraProvider } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' import { MockedProvider } from '@apollo/client/testing' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { makeVar } from '@apollo/client' -import { en } from 'make-plural/plurals' - import { UserList } from '../UserList' - import { UserVarProvider } from '../../utilities/userState' import { createCache } from '../../client' import { PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE as FORWARD } from '../../graphql/queries' -import { - UPDATE_USER_ROLE, - INVITE_USER_TO_ORG, - REMOVE_USER_FROM_ORG, -} from '../../graphql/mutations' +import { UPDATE_USER_ROLE, INVITE_USER_TO_ORG, REMOVE_USER_FROM_ORG } from '../../graphql/mutations' import { rawOrgUserListData } from '../../fixtures/orgUserListData' -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) - const successMocks = [ { request: { query: FORWARD, - variables: { first: 10, orgSlug: 'test-org.slug', search: '' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + includePending: true, + orderBy: { direction: 'ASC', field: 'PERMISSION' }, + }, }, result: { data: rawOrgUserListData }, }, { request: { query: FORWARD, - variables: { first: 10, orgSlug: 'test-org.slug' }, + variables: { + first: 50, + orgSlug: 'test-org.slug', + search: '', + includePending: true, + orderBy: { direction: 'ASC', field: 'PERMISSION' }, + }, }, result: { data: rawOrgUserListData }, }, @@ -49,9 +44,7 @@ const successMocks = [ request: { query: UPDATE_USER_ROLE, variables: { - userName: - rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node - .user.userName, + userName: rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node.user.userName, orgId: rawOrgUserListData.findOrganizationBySlug.id, role: 'ADMIN', }, @@ -75,7 +68,6 @@ const successMocks = [ userName: 'newUser@test.ca', requestedRole: 'USER', orgId: rawOrgUserListData.findOrganizationBySlug.id, - preferredLang: 'ENGLISH', }, }, result: { @@ -94,9 +86,7 @@ const successMocks = [ request: { query: REMOVE_USER_FROM_ORG, variables: { - userId: - rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node - .user.id, + userId: rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node.user.id, orgId: rawOrgUserListData.findOrganizationBySlug.id, }, }, @@ -122,15 +112,13 @@ describe('', () => { it('successfully renders with mocked data', async () => { const { getByText } = render( - + @@ -141,14 +129,7 @@ describe('', () => { , ) - await waitFor(() => - expect( - getByText( - rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node - .user.userName, - ), - ).toBeInTheDocument(), - ) + await waitFor(() => expect(getByText(/test.user@email.com/i)).toBeInTheDocument()) }) describe('Admin profile userlist', () => { @@ -156,13 +137,7 @@ describe('', () => { // edit success it('updateUserRole elements render', async () => { - const { - getAllByText, - getByDisplayValue, - getByText, - findByLabelText, - findAllByLabelText, - } = render( + const { getAllByText, getByDisplayValue, getByText, findByLabelText, findAllByLabelText } = render( ', () => { @@ -233,8 +208,9 @@ describe('', () => { @@ -272,8 +248,8 @@ describe('', () => { @@ -285,7 +261,7 @@ describe('', () => { ) await waitFor(() => { - expect(queryByText(/Invite User/)).toBeInTheDocument() + expect(queryByText(/Garland.Hudson@yahoo.com/)).toBeInTheDocument() }) const userRemoveButtons = getAllByLabelText(/Remove User/) diff --git a/frontend/src/admin/__tests__/UserListModal.test.js b/frontend/src/admin/__tests__/UserListModal.test.js index f2b50b412b..d21c830849 100644 --- a/frontend/src/admin/__tests__/UserListModal.test.js +++ b/frontend/src/admin/__tests__/UserListModal.test.js @@ -4,28 +4,16 @@ import { MemoryRouter } from 'react-router-dom' import { theme, ChakraProvider, useDisclosure } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' import { MockedProvider } from '@apollo/client/testing' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { makeVar } from '@apollo/client' -import { en } from 'make-plural/plurals' - import { UserVarProvider } from '../../utilities/userState' import { createCache } from '../../client' -import { UPDATE_USER_ROLE, INVITE_USER_TO_ORG } from '../../graphql/mutations' +import { UPDATE_USER_ROLE, INVITE_USER_TO_ORG, REMOVE_USER_FROM_ORG } from '../../graphql/mutations' import { UserListModal } from '../UserListModal' import userEvent from '@testing-library/user-event' import canada from '../../theme/canada' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) - const orgId = 'test-id' +const editingUserId = 'test-id' const editingUserName = 'test-username@email.com' const orgSlug = 'test-org-slug' @@ -40,6 +28,7 @@ const UserListModalExample = ({ mutation, permission, editingUserRole }) => { isOpen={isOpen} onClose={onClose} orgId={orgId} + editingUserId={editingUserId} editingUserName={editingUserName} editingUserRole={editingUserRole} orgSlug={orgSlug} @@ -54,17 +43,11 @@ describe('', () => { it('can be opened and closed', async () => { const { getByRole, queryByText } = render( - + - + @@ -87,12 +70,220 @@ describe('', () => { // modal closed userEvent.click(closeModalButton) - await waitFor(() => - expect(queryByText(/test-username/)).not.toBeInTheDocument(), - ) + await waitFor(() => expect(queryByText(/test-username/)).not.toBeInTheDocument()) }) describe('admin is updating a user', () => { + describe('an error is displayed when', () => { + it('a server-side error occurs', async () => { + const mocks = [ + { + request: { + query: UPDATE_USER_ROLE, + variables: { + orgId: orgId, + role: 'ADMIN', + userName: editingUserName, + }, + }, + result: { + error: { errors: [{ message: 'error' }] }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const roleChangeSelect = getByRole('combobox', { + name: /Role:/, + }) + expect(roleChangeSelect.options.length).toEqual(2) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + + // select new role and update + userEvent.selectOptions(roleChangeSelect, 'ADMIN') + const confirmUserUpdateButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserUpdateButton) + + // check for "error" toast + await waitFor(() => { + expect(getAllByText(/Unable to change user role, please try again./)[0]) + }) + }) + it('a client-side error occurs', async () => { + const mocks = [ + { + request: { + query: UPDATE_USER_ROLE, + variables: { + orgId: orgId, + role: 'ADMIN', + userName: editingUserName, + }, + }, + result: { + data: { + updateUserRole: { + result: { + description: 'User role was updated successfully', + __typename: 'AffiliationError', + }, + __typename: 'UpdateUserRolePayload', + }, + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const roleChangeSelect = getByRole('combobox', { + name: /Role:/, + }) + expect(roleChangeSelect.options.length).toEqual(2) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + + // select new role and update + userEvent.selectOptions(roleChangeSelect, 'ADMIN') + const confirmUserUpdateButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserUpdateButton) + + // check for "error" toast + await waitFor(() => { + expect(getAllByText(/Unable to update user role./)[0]).toBeInTheDocument() + }) + }) + it('a type error occurs', async () => { + const mocks = [ + { + request: { + query: UPDATE_USER_ROLE, + variables: { + orgId: orgId, + role: 'ADMIN', + userName: editingUserName, + }, + }, + result: { + data: { + updateUserRole: { + result: { + description: 'User role was updated successfully', + __typename: 'TypeError', + }, + __typename: 'UpdateUserRolePayload', + }, + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const roleChangeSelect = getByRole('combobox', { + name: /Role:/, + }) + expect(roleChangeSelect.options.length).toEqual(2) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + + // select new role and update + userEvent.selectOptions(roleChangeSelect, 'ADMIN') + const confirmUserUpdateButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserUpdateButton) + + // check for "error" toast + await waitFor(() => { + expect(getAllByText(/Incorrect send method received./)[0]).toBeInTheDocument() + }) + }) + }) describe('admin has "ADMIN" privileges', () => { describe('admin is updating user with "USER" privileges', () => { it('admin can change user role to "ADMIN"', async () => { @@ -120,7 +311,7 @@ describe('', () => { }, ] - const { getAllByText, queryByRole, getByRole, queryByText } = render( + const { getAllByText, getByRole, queryByText } = render( ', () => { - + @@ -156,12 +343,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -172,17 +355,46 @@ describe('', () => { // check for "success" toast await waitFor(() => { - expect( - getAllByText(/The user's role has been successfully updated/)[0], - ).toBeVisible() + expect(getAllByText(/The user's role has been successfully updated/)[0]).toBeInTheDocument() }) + }) + it('admin can not change user role to "OWNER"', async () => { + const { getByRole, queryByText } = render( + + + + + + + + + + + , + ) - // wait for modal to close - await waitFor(() => { - expect( - queryByRole('combobox', { name: /Role:/ }), - ).not.toBeInTheDocument() + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const roleChangeSelect = getByRole('combobox', { + name: /Role:/, }) + expect(roleChangeSelect.options.length).toEqual(2) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + expect(roleChangeSelect).not.toHaveTextContent(/OWNER/) + expect(roleChangeSelect).not.toHaveTextContent(/SUPER_ADMIN/) }) it('admin can not change user role to "SUPER_ADMIN"', async () => { const { getByRole, queryByText } = render( @@ -197,11 +409,7 @@ describe('', () => { - + @@ -221,17 +429,13 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) expect(roleChangeSelect).not.toHaveTextContent(/SUPER_ADMIN/) }) }) describe('admin is updating user with "ADMIN" privileges', () => { - it('admin cannot downgrade user to "USER"', async () => { + it('admin can downgrade user to "USER"', async () => { const { getByRole, queryByText } = render( ', () => { - + @@ -267,11 +467,11 @@ describe('', () => { const roleChangeSelect = getByRole('combobox', { name: /Role:/, }) - expect(roleChangeSelect.options.length).toEqual(1) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /ADMIN/, - ) - expect(roleChangeSelect).not.toHaveTextContent(/USER/) + expect(roleChangeSelect.options.length).toEqual(2) + expect(roleChangeSelect.value).toEqual('ADMIN') + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + expect(roleChangeSelect).toHaveTextContent(/USER/) }) }) }) @@ -302,7 +502,7 @@ describe('', () => { }, ] - const { getAllByText, queryByRole, getByRole, queryByText } = render( + const { getAllByText, getByRole, queryByText } = render( ', () => { - + @@ -337,13 +533,9 @@ describe('', () => { const roleChangeSelect = getByRole('combobox', { name: /Role:/, }) - expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(roleChangeSelect.options.length).toEqual(3) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -355,21 +547,36 @@ describe('', () => { // check for "success" toast await waitFor(() => { - expect( - getAllByText(/The user's role has been successfully updated/)[0], - ).toBeVisible() - }) - - // wait for modal to close - await waitFor(() => { - expect( - queryByRole('combobox', { name: /Role:/ }), - ).not.toBeInTheDocument() + expect(getAllByText(/The user's role has been successfully updated/)[0]).toBeInTheDocument() }) }) - it('admin can not change user role to "SUPER_ADMIN"', async () => { - const { getByRole, queryByText } = render( - + it('admin can change user role to "OWNER"', async () => { + const mocks = [ + { + request: { + query: UPDATE_USER_ROLE, + variables: { + orgId: orgId, + role: 'OWNER', + userName: editingUserName, + }, + }, + result: { + data: { + updateUserRole: { + result: { + status: 'User role was updated successfully', + __typename: 'UpdateUserRoleResult', + }, + __typename: 'UpdateUserRolePayload', + }, + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + ', () => { - + @@ -403,38 +606,271 @@ describe('', () => { const roleChangeSelect = getByRole('combobox', { name: /Role:/, }) - expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) - expect(roleChangeSelect).not.toHaveTextContent(/SUPER_ADMIN/) + expect(roleChangeSelect.options.length).toEqual(3) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + expect(Object.values(roleChangeSelect.options)[2]).toHaveTextContent(/OWNER/) + + // select new role and update + userEvent.selectOptions(roleChangeSelect, 'OWNER') + const confirmUserUpdateButton = getByRole('button', { + name: /Confirm/i, + }) + + userEvent.click(confirmUserUpdateButton) + + // check for "success" toast + await waitFor(() => { + expect(getAllByText(/The user's role has been successfully updated/)[0]).toBeInTheDocument() + }) }) - }) - }) - }) - describe('admin is adding a new user', () => { - describe('admin has "ADMIN" privileges', () => { - it('admin can add a user with "USER" privileges', async () => { - const mocks = [ - { - request: { - query: INVITE_USER_TO_ORG, - variables: { + it('admin can not change user role to "SUPER_ADMIN"', async () => { + const { getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const roleChangeSelect = getByRole('combobox', { + name: /Role:/, + }) + expect(roleChangeSelect.options.length).toEqual(3) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + expect(roleChangeSelect).not.toHaveTextContent(/SUPER_ADMIN/) + }) + }) + }) + }) + + describe('admin is removing a user', () => { + describe('an error is displayed when', () => { + it('a server-side error occurs', async () => { + const mocks = [ + { + request: { + query: REMOVE_USER_FROM_ORG, + variables: { + orgId, + userId: editingUserId, + }, + }, + result: { + error: { errors: [{ message: 'error' }] }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + const confirmUserUpdateButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserUpdateButton) + + // check for "error" toast + await waitFor(() => { + expect(getAllByText(/An error occurred./)[0]) + }) + }) + it('a client-side error occurs', async () => { + const mocks = [ + { + request: { + query: REMOVE_USER_FROM_ORG, + variables: { + orgId, + userId: editingUserId, + }, + }, + result: { + data: { + removeUserFromOrg: { + result: { + code: 100, + description: 'User role was updated successfully', + __typename: 'AffiliationError', + }, + }, + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-username/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + const confirmUserUpdateButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserUpdateButton) + + // check for "error" toast + await waitFor(() => { + expect(getAllByText(/Unable to remove user./)[0]) + }) + }) + }) + }) + + describe('admin is adding a new user', () => { + describe('when an error occurs', () => { + it('server-side error', async () => { + const mocks = [ + { + request: { + query: INVITE_USER_TO_ORG, + variables: { + orgId: orgId, + requestedRole: 'USER', + userName: editingUserName, + }, + }, + result: { + error: { + errors: [{ message: 'error' }], + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + + await waitFor(() => { + expect(queryByText(/test-username/)).not.toBeInTheDocument() + }) + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const newUserRoleSelect = getByRole('combobox', { + name: /Role:/, + }) + + expect(newUserRoleSelect.options.length).toEqual(2) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) + + const confirmUserInviteButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserInviteButton) + + // check for "success" toast and modal to close + await waitFor(() => { + expect(getAllByText(/An error occurred./)[0]).toBeVisible() + }) + }) + it.skip('client-side error', async () => { + const mocks = [ + { + request: { + query: INVITE_USER_TO_ORG, + variables: { orgId: orgId, requestedRole: 'USER', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { data: { inviteUserToOrg: { result: { - status: 'User successfully invited', - __typename: 'InviteUserToOrgResult', + description: 'User successfully invited', + __typename: 'AffiliationError', }, __typename: 'InviteUserToOrgPayload', }, @@ -443,7 +879,7 @@ describe('', () => { }, ] - const { getAllByText, queryByRole, getByRole, queryByText } = render( + const { getAllByText, getByRole, queryByText } = render( ', () => { - + @@ -469,7 +901,10 @@ describe('', () => { // modal closed const openModalButton = getByRole('button', { name: /Open Modal/ }) - expect(queryByText(/test-username/)).not.toBeInTheDocument() + + await waitFor(() => { + expect(queryByText(/test-username/)).not.toBeInTheDocument() + }) // modal opened userEvent.click(openModalButton) @@ -480,13 +915,83 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) + + const confirmUserInviteButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserInviteButton) + + // check for "success" toast and modal to close + await waitFor(() => { + expect(getAllByText(/Unable to invite user./)[0]).toBeVisible() + }) + }) + it('incorrect typename error', async () => { + const mocks = [ + { + request: { + query: INVITE_USER_TO_ORG, + variables: { + orgId: orgId, + requestedRole: 'USER', + userName: editingUserName, + }, + }, + result: { + data: { + inviteUserToOrg: { + result: { + status: 'User successfully invited', + __typename: 'TyperError', + }, + __typename: 'InviteUserToOrgPayload', + }, + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , ) + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + + await waitFor(() => { + expect(queryByText(/test-username/)).not.toBeInTheDocument() + }) + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const newUserRoleSelect = getByRole('combobox', { + name: /Role:/, + }) + + expect(newUserRoleSelect.options.length).toEqual(2) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) + const confirmUserInviteButton = getByRole('button', { name: /Confirm/i, }) @@ -494,15 +999,93 @@ describe('', () => { // check for "success" toast and modal to close await waitFor(() => { - expect(getAllByText(/Email invitation sent/)[0]).toBeVisible() + expect(getAllByText(/Incorrect send method received./)[0]).toBeVisible() }) + }) + }) + + describe('admin has "ADMIN" privileges', () => { + it.skip('admin can add a user with "USER" privileges', async () => { + const mocks = [ + { + request: { + query: INVITE_USER_TO_ORG, + variables: { + orgId: orgId, + requestedRole: 'USER', + userName: editingUserName, + }, + }, + result: { + data: { + inviteUserToOrg: { + result: { + status: 'User successfully invited', + __typename: 'InviteUserToOrgResult', + }, + __typename: 'InviteUserToOrgPayload', + }, + }, + }, + }, + ] + + const { getAllByText, getByRole, queryByText } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) - // wait for modal to close await waitFor(() => { - expect( - queryByRole('combobox', { name: /Role:/ }), - ).not.toBeInTheDocument() + expect(queryByText(/test-username/)).not.toBeInTheDocument() }) + + // modal opened + userEvent.click(openModalButton) + + // get select element, verify options + const newUserRoleSelect = getByRole('combobox', { + name: /Role:/, + }) + + expect(newUserRoleSelect.options.length).toEqual(2) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) + + const confirmUserInviteButton = getByRole('button', { + name: /Confirm/i, + }) + userEvent.click(confirmUserInviteButton) + + // check for "success" toast and modal to close + await waitFor(() => { + expect(getAllByText(/Email invitation sent/)[0]).toBeInTheDocument() + }) + + // wait for modal to close + await waitFor( + () => { + expect(newUserRoleSelect).not.toBeInTheDocument() + }, + { timeout: 2000 }, + ) }) }) it('admin can add a user with "ADMIN" privileges', async () => { @@ -514,7 +1097,6 @@ describe('', () => { orgId: orgId, requestedRole: 'ADMIN', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -531,7 +1113,7 @@ describe('', () => { }, ] - const { getAllByText, queryByRole, getByRole, queryByText } = render( + const { getAllByText, getByRole, queryByText } = render( ', () => { - + @@ -568,12 +1146,8 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) userEvent.selectOptions(newUserRoleSelect, 'ADMIN') const confirmUserInviteButton = getByRole('button', { @@ -583,14 +1157,7 @@ describe('', () => { // check for "success" toast await waitFor(() => { - expect(getAllByText(/Email invitation sent/)[0]).toBeVisible() - }) - - // wait for modal to close - await waitFor(() => { - expect( - queryByRole('combobox', { name: /Role:/ }), - ).not.toBeInTheDocument() + expect(getAllByText(/Email invitation sent/)[0]).toBeInTheDocument() }) }) }) diff --git a/frontend/src/app/ABTestWrapper.js b/frontend/src/app/ABTestWrapper.js new file mode 100644 index 0000000000..6d1177b928 --- /dev/null +++ b/frontend/src/app/ABTestWrapper.js @@ -0,0 +1,40 @@ +import React from 'react' +import { any, string } from 'prop-types' +import { useUserVar } from '../utilities/userState' + +export function ABTestVariant({ children }) { + return <>{children} +} + +export function ABTestWrapper({ children, insiderVariantName = 'B' }) { + const { + currentUser: { insideUser }, + } = useUserVar() + let childIndex = 0 + + // only one variant + if (!children.length) { + if (insideUser) { + if (children.props.name === insiderVariantName) return <>{children} + else return <> + } else { + if (children.props.name !== insiderVariantName) return <>{children} + else return <> + } + } + // A + B variants + if (insideUser) { + childIndex = children.findIndex((variant) => variant.props.name === insiderVariantName) + } else { + childIndex = children.findIndex((variant) => variant.props.name !== insiderVariantName) + } + return <>{children[childIndex]} +} + +ABTestVariant.propTypes = { + children: any, +} +ABTestWrapper.propTypes = { + insiderVariantName: string, + children: any, +} diff --git a/frontend/src/app/App.js b/frontend/src/app/App.js index ea62d5f245..b0cf9ebc19 100644 --- a/frontend/src/app/App.js +++ b/frontend/src/app/App.js @@ -1,307 +1,446 @@ -import React, { Suspense, useEffect } from 'react' -import { Switch, Link as RouteLink, Redirect } from 'react-router-dom' -import { i18n } from '@lingui/core' -import { CSSReset, Flex, Link } from '@chakra-ui/react' -import { t, Trans } from '@lingui/macro' -import { ErrorBoundary } from 'react-error-boundary' +import React, { Suspense } from 'react' +import { Link as RouteLink, Routes, Route, Navigate } from 'react-router-dom' +import { AlertDescription, AlertTitle, Box, Code, CSSReset, Flex, Link, Skeleton, Text } from '@chakra-ui/react' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { Main } from './Main' import { TopBanner } from './TopBanner' -import { PhaseBanner } from './PhaseBanner' import { Footer } from './Footer' import { Navigation } from './Navigation' import { SkipLink } from './SkipLink' import { FloatingMenu } from './FloatingMenu' -import { PrivatePage } from './PrivatePage' -import { Page } from './Page' -import { RequestScanNotificationHandler } from './RequestScanNotificationHandler' -import { VerifyAccountNotificationBar } from './VerifyAccountNotificationBar' -import { wsClient } from '../client' import { LoadingMessage } from '../components/LoadingMessage' -import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { useUserVar } from '../utilities/userState' import { lazyWithRetry } from '../utilities/lazyWithRetry' import { LandingPage } from '../landing/LandingPage' +import { NotificationBanner } from './NotificationBanner' +import { useLingui } from '@lingui/react' +import { ScrollToAnchor } from './ScrollToAnchor' +import { bool } from 'prop-types' +import { Page } from './Page' +import { PrivatePage } from './PrivatePage' + +const GuidancePage = lazyWithRetry(() => import('../guidance/GuidancePage')) const PageNotFound = lazyWithRetry(() => import('./PageNotFound')) const CreateUserPage = lazyWithRetry(() => import('../auth/CreateUserPage')) const DomainsPage = lazyWithRetry(() => import('../domains/DomainsPage')) const UserPage = lazyWithRetry(() => import('../user/UserPage')) const SignInPage = lazyWithRetry(() => import('../auth/SignInPage')) const DmarcReportPage = lazyWithRetry(() => import('../dmarc/DmarcReportPage')) -const Organizations = lazyWithRetry(() => - import('../organizations/Organizations'), -) -const OrganizationDetails = lazyWithRetry(() => - import('../organizationDetails/OrganizationDetails'), -) +const Organizations = lazyWithRetry(() => import('../organizations/Organizations')) +const OrganizationDetails = lazyWithRetry(() => import('../organizationDetails/OrganizationDetails')) const AdminPage = lazyWithRetry(() => import('../admin/AdminPage')) -const ForgotPasswordPage = lazyWithRetry(() => - import('../auth/ForgotPasswordPage'), -) -const ResetPasswordPage = lazyWithRetry(() => - import('../auth/ResetPasswordPage'), -) -const DmarcByDomainPage = lazyWithRetry(() => - import('../dmarc/DmarcByDomainPage'), -) -const DmarcGuidancePage = lazyWithRetry(() => - import('../guidance/DmarcGuidancePage'), -) -const TermsConditionsPage = lazyWithRetry(() => - import('../termsConditions/TermsConditionsPage'), -) -const TwoFactorAuthenticatePage = lazyWithRetry(() => - import('../auth/TwoFactorAuthenticatePage'), -) -const EmailValidationPage = lazyWithRetry(() => - import('../auth/EmailValidationPage'), -) -const CreateOrganizationPage = lazyWithRetry(() => - import('../createOrganization/CreateOrganizationPage'), -) - -export function App() { +const ForgotPasswordPage = lazyWithRetry(() => import('../auth/ForgotPasswordPage')) +const ResetPasswordPage = lazyWithRetry(() => import('../auth/ResetPasswordPage')) +const DmarcByDomainPage = lazyWithRetry(() => import('../dmarc/DmarcByDomainPage')) +const TermsConditionsPage = lazyWithRetry(() => import('../termsConditions/TermsConditionsPage')) +const TwoFactorAuthenticatePage = lazyWithRetry(() => import('../auth/TwoFactorAuthenticatePage')) +const EmailValidationPage = lazyWithRetry(() => import('../auth/EmailValidationPage')) +const CreateOrganizationPage = lazyWithRetry(() => import('../createOrganization/CreateOrganizationPage')) +const ContactUsPage = lazyWithRetry(() => import('./ContactUsPage')) +const ReadGuidancePage = lazyWithRetry(() => import('./ReadGuidancePage')) +const MyTrackerPage = lazyWithRetry(() => import('../user/MyTrackerPage')) + +export function App({ initialLoading, isLoginRequired }) { // Hooks to be used with this functional component - const { currentUser, isLoggedIn, isEmailValidated } = useUserVar() - - // Close websocket on user jwt change (refresh/logout) - // Ready state documented at: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState - useEffect(() => { - // User is logged out and websocket connection is active - if (currentUser?.jwt === '' && [0, 1].includes(wsClient.status)) { - wsClient.close() + const { currentUser, isLoggedIn, isEmailValidated, currentTFAMethod, hasAffiliation } = useUserVar() + const { i18n } = useLingui() + + const notificationBanner = () => { + if (isLoggedIn()) { + if (isEmailValidated()) { + if (currentTFAMethod() === 'NONE') { + return ( + + + + To maximize your account's security,{' '} + + please activate a multi-factor authentication option + + . + + + + ) + } + if (!hasAffiliation()) { + return ( + + + + To view detailed scan results and other functionality,{' '} + + please affiliate with an organization + + . + + + + ) + } + } else { + return ( + + + + To enable full app functionality and maximize your account's security,{' '} + + please verify your account + + . + + + + ) + } } - }, [currentUser.jwt]) + } return ( - <> - - -
- - - Skip to main content - - BETA}> - This is a new service, we are constantly improving. - - -
- - - - Home - - - {isLoggedIn() && isEmailValidated() && ( - <> - - Organizations - - - Domains - + + +
+ + + Skip to main content + + +
+ + + Home + + + {initialLoading ? ( + <> + + LoadingState + + + LoadingState + + + ) : ( + <> + {((isLoggedIn() && isEmailValidated()) || !isLoginRequired) && ( + <> + + Organizations + + + Domains + + + )} + + {isLoggedIn() && isEmailValidated() && currentTFAMethod() !== 'NONE' && ( DMARC Summaries - - )} - - {isLoggedIn() && ( - - Account Settings - - )} - - {isLoggedIn() && isEmailValidated() && ( - - Admin Profile - - )} - - - {isLoggedIn() && !isEmailValidated() && ( - + )} + + {isLoggedIn() && ( + <> + + myTracker + + + + Account Settings + + + )} + + {isLoggedIn() && isEmailValidated() && currentTFAMethod() !== 'NONE' && ( + <> + + Admin Profile + + + )} + )} - -
+ + + {window.env?.APP_IS_PRODUCTION !== true ? ( + + + + You are currently visiting a staging environment, used for testing purposes only. This is{' '} + not the live production site. +
+ Visit the production site at{' '} + + https://tracker.canada.ca + {' '} + (GC network only). +
+
+
+ ) : null} + + {notificationBanner()} + + + + + Tracker now automatically manages your DKIM selectors. + + + + Manual management of DKIM selectors is discontinued. DKIM selectors will automatically be added when + setting rua=mailto:dmarc@cyber.gc.ca in your DMARC record.{' '} + + Learn more + + . + + + + + +
+ {initialLoading ? ( + + ) : ( }> - - - - + + + + + } + exact + path="/" + /> - + + + } path="/create-user/:userOrgToken?" - title={t`Create an Account`} - > - - - - { - return isLoggedIn() ? ( - - ) : ( - - ) - }} /> - : } /> + + + + + } /> - + + + } /> - + + + } /> - + + + } /> - - {() => ( - + + + + } + /> + + + + + } + /> + + - - )} - + + } + /> - - {() => ( - + element={ + - - )} - - - - {() => ( - - - - )} - - - - {() => ( - + + } + /> + + + + + } + /> + + - - )} - - - - {() => ( - - - - )} - - - + } + /> + + + + + } + /> + + - {() => ( - + element={ + - - )} - + + } + /> - - {() => ( - + element={ + - - )} - - - - {isLoggedIn() ? ( - - ) : ( - - )} - - - - {() => } - - - + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + - {() => } - + element={ + + + + } + /> - - + + + + } + /> + -
- - -
- - Privacy - - - - Terms & conditions - - - - Report an Issue - -
- - + )} +
+ + +
+ + Privacy + + + + Terms & conditions + + + + Contact Us + + + + Guidance + +
+
) } + +App.propTypes = { + initialLoading: bool, + isLoginRequired: bool, +} diff --git a/frontend/src/app/ContactUsPage.js b/frontend/src/app/ContactUsPage.js new file mode 100644 index 0000000000..7ac31800b9 --- /dev/null +++ b/frontend/src/app/ContactUsPage.js @@ -0,0 +1,73 @@ +import React from 'react' +import { Trans } from "@lingui/react/macro" +import { Box, Divider, Heading, Button, Text, Link } from '@chakra-ui/react' +import { Link as RouteLink } from 'react-router-dom' + +export default function ContactUsPage() { + return ( + + + Contact the Tracker Team + + + + + + Read Guidance + + + + The{' '} + + "Getting Started" guide and FAQ section + {' '} + will help you to understand Tracker's capabilities and limitations. + + + + + + + Government of Canada Employees + + + + Individuals from a departmental information technology group may contact the TBS Cyber Security mailbox + for results interpretation and domain management. + + + + + For questions and issues related to scan data, your organization's domain list, or getting help onboarding + users, please contact TBS Cyber Security. + + + + + + + + Tracker GitHub + + + + Submit bug reports and feature requests through our{' '} + + GitHub page + + . + + + + + + ) +} diff --git a/frontend/src/app/FloatingMenu.js b/frontend/src/app/FloatingMenu.js index 010235185c..e7efbce810 100644 --- a/frontend/src/app/FloatingMenu.js +++ b/frontend/src/app/FloatingMenu.js @@ -18,7 +18,8 @@ import { useDisclosure, useToast, } from '@chakra-ui/react' -import { t, Trans } from '@lingui/macro' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { useLingui } from '@lingui/react' import { useMutation } from '@apollo/client' @@ -35,7 +36,7 @@ export const FloatingMenu = () => { onError(error) { toast({ title: error.message, - description: t`An error occured when you attempted to sign out`, + description: t`An error occurred when you attempted to sign out`, status: 'error', duration: 9000, isClosable: true, @@ -230,6 +231,14 @@ export const FloatingMenu = () => { text={t`Report an Issue`} isExternal /> + + diff --git a/frontend/src/app/Footer.js b/frontend/src/app/Footer.js index 2981d2a2fb..2590f67b7a 100644 --- a/frontend/src/app/Footer.js +++ b/frontend/src/app/Footer.js @@ -10,12 +10,7 @@ export const Footer = ({ children, ...props }) => { const { i18n } = useLingui() return ( - + {children} { _focus={{ outline: `3px solid accent`, }} - bg="primary" - color="#fff" + _hover={{ + bg: 'gray.200', + }} > {locales[locale]} - {locale} + + {locale} + ) } -export function LocaleSwitcher() { +export function LocaleSwitcher({ ...props }) { const { i18n } = useLingui() return ( - + {i18n.locale === 'en' && } {i18n.locale === 'fr' && } diff --git a/frontend/src/app/Main.js b/frontend/src/app/Main.js index d13657aa81..fe16ceb07a 100644 --- a/frontend/src/app/Main.js +++ b/frontend/src/app/Main.js @@ -1,10 +1,11 @@ import React from 'react' -import { node } from 'prop-types' +import { bool, node } from 'prop-types' import { Flex } from '@chakra-ui/react' export const Main = ({ children }) => ( ( ) -Main.propTypes = { children: node } +Main.propTypes = { children: node, slideMessageOpen: bool } diff --git a/frontend/src/app/Navigation.js b/frontend/src/app/Navigation.js index 533f0ec987..cf55f2f732 100644 --- a/frontend/src/app/Navigation.js +++ b/frontend/src/app/Navigation.js @@ -7,25 +7,21 @@ export const Navigation = ({ children, ...props }) => { return ( {React.Children.map(children, (child) => { if (child !== null) { diff --git a/frontend/src/app/NotificationBanner.js b/frontend/src/app/NotificationBanner.js new file mode 100644 index 0000000000..c1d352009c --- /dev/null +++ b/frontend/src/app/NotificationBanner.js @@ -0,0 +1,145 @@ +import React, { useEffect, useState } from 'react' +import { Flex, Box, CloseButton, Button, Alert, AlertIcon, useToast } from '@chakra-ui/react' + +import { any, bool, oneOf, string } from 'prop-types' +import { CloseIcon } from '@chakra-ui/icons' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { useMutation } from '@apollo/client' +import { DISMISS_MESSAGE } from '../graphql/mutations' +import { useUserVar } from '../utilities/userState' + +export function NotificationBanner({ children, hideable = false, bannerId, status = 'info', ...props }) { + const toast = useToast() + const { login, currentUser, isLoggedIn } = useUserVar() + + function checkHideBanner({ dismissedMessages }) { + const dismissedMessage = dismissedMessages.find((message) => message.messageId === bannerId) + return !!dismissedMessage + } + + const [hideBanner, setHideBanner] = useState( + checkHideBanner({ dismissedMessages: currentUser?.dismissedMessages || [], userLoggedIn: isLoggedIn() }), + ) + + const [dismissMessage, { loading, _error }] = useMutation(DISMISS_MESSAGE, { + onError: ({ message }) => { + toast({ + title: t`An error occurred when dismissing the message.`, + description: message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ dismissMessage }) { + if (dismissMessage.result.__typename === 'DismissMessageResult') { + toast({ + title: t`Message dismissed successfully`, + description: t`The message has been dismissed and will not be shown again.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + login({ + ...currentUser, + dismissedMessages: dismissMessage.result.user.dismissedMessages, + }) + } else if (dismissMessage.result.__typename === 'DismissMessageError') { + toast({ + title: t`Unable to dismiss the message.`, + description: dismissMessage.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: t`Incorrect dismiss method received.`, + description: t`Incorrect dismissMessage.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } + }, + }) + + useEffect(() => { + const shouldHideBanner = checkHideBanner({ + dismissedMessages: currentUser?.dismissedMessages || [], + userLoggedIn: isLoggedIn(), + }) + + if (shouldHideBanner !== hideBanner) { + setHideBanner(shouldHideBanner) + } + }, [currentUser?.dismissedMessages]) + + const handleClose = () => { + setHideBanner(true) + } + + const handleDontShowAgain = async () => { + await dismissMessage({ + variables: { + messageId: bannerId, + }, + }) + } + + if (hideBanner) { + return null + } + + return ( + + + + + + {children} + + {hideable && ( + + {bannerId && isLoggedIn() && ( + + )} + + + + + )} + + + + ) +} + +NotificationBanner.propTypes = { + hideable: bool, + bannerId: string, + status: oneOf(['info', 'warning', 'success', 'error', 'loading']), + children: any, +} diff --git a/frontend/src/app/Page.js b/frontend/src/app/Page.js index cbccb5c731..6ba517c0d4 100644 --- a/frontend/src/app/Page.js +++ b/frontend/src/app/Page.js @@ -1,16 +1,16 @@ import React from 'react' -import { Route } from 'react-router-dom' -import { bool, string } from 'prop-types' - +import { any, bool, string } from 'prop-types' import { useDocumentTitle } from '../utilities/useDocumentTitle' +import { ErrorBoundary } from 'react-error-boundary' +import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' -export const Page = ({ title, setTitle, ...props }) => { +export const Page = ({ title, setTitle, children }) => { useDocumentTitle(title, setTitle) - - return + return {children} } Page.propTypes = { title: string, setTitle: bool, + children: any, } diff --git a/frontend/src/app/PageNotFound.js b/frontend/src/app/PageNotFound.js index 33a6f62a19..36b597cbd1 100644 --- a/frontend/src/app/PageNotFound.js +++ b/frontend/src/app/PageNotFound.js @@ -1,7 +1,7 @@ import React from 'react' import { Box, Divider, Heading, Stack, Text } from '@chakra-ui/react' import { WarningTwoIcon } from '@chakra-ui/icons' -import { Trans } from '@lingui/macro' +import { Trans } from "@lingui/react/macro" export default function PageNotFound() { return ( diff --git a/frontend/src/app/PhaseBanner.js b/frontend/src/app/PhaseBanner.js index eeec472849..c013489782 100644 --- a/frontend/src/app/PhaseBanner.js +++ b/frontend/src/app/PhaseBanner.js @@ -2,25 +2,21 @@ import React from 'react' import PropTypes from 'prop-types' import { Flex, Tag, Text } from '@chakra-ui/react' -import { Layout } from '../components/Layout' - -export function PhaseBanner({ phase, children }) { +export function PhaseBanner({ phase, children, ...props }) { return ( - - - - {phase} - - {children} - - + + + {phase} + + {children} + ) } diff --git a/frontend/src/app/PrivatePage.js b/frontend/src/app/PrivatePage.js index 482dfd85ad..6c375efade 100644 --- a/frontend/src/app/PrivatePage.js +++ b/frontend/src/app/PrivatePage.js @@ -1,36 +1,19 @@ import React from 'react' -import { func } from 'prop-types' -import { Redirect, useLocation } from 'react-router-dom' - +import { bool, func } from 'prop-types' +import { Navigate, useLocation } from 'react-router-dom' import { Page } from './Page' -import { useUserVar } from '../utilities/userState' - // A wrapper for that redirects to the login // screen if you're not yet authenticated. -export function PrivatePage({ children, ...rest }) { - const { isLoggedIn, isEmailValidated } = useUserVar() +export function PrivatePage({ children, condition, ...rest }) { const location = useLocation() - return ( - - isLoggedIn() && isEmailValidated() ? ( - children(props) - ) : ( - - ) - } - /> - ) + + return {condition ? children : } } PrivatePage.propTypes = { children: func, + isLoginRequired: bool, + condition: bool, ...Page.propTypes, } diff --git a/frontend/src/app/ReadGuidancePage.js b/frontend/src/app/ReadGuidancePage.js new file mode 100644 index 0000000000..08f8137126 --- /dev/null +++ b/frontend/src/app/ReadGuidancePage.js @@ -0,0 +1,604 @@ +import React from 'react' +import { Trans } from "@lingui/react/macro" +import { Box, Heading, Text, Link, ListItem, OrderedList, UnorderedList, Divider, Code } from '@chakra-ui/react' +import { useLingui } from '@lingui/react' + +export default function ReadGuidancePage() { + const { i18n } = useLingui() + + return ( + + + Getting Started + + + + + + The Government of Canada’s (GC){' '} + + Directive on Service and Digital + {' '} + provides expectations on how GC organizations are to manage their Information Technology (IT) services. The + focus of the Tracker tool is to help organizations stay in compliance with the directives{' '} + + Email Management Service Configuration Requirements + {' '} + and the directives{' '} + + Web Site and Service Management Configuration Requirements + + . + + + + Below are steps on how government organizations can leverage the Tracker platform: + + + {/* 1 */} + + + Getting an Account: + + + + + + Identify any current affiliated Tracker users within your organization and develop a plan with them. + + + + + + + If your organization has no affiliated users within Tracker, contact the{' '} + + TBS Cyber Security + {' '} + to assist in onboarding. + + + + + + + Once access is given to your department by the TBS Cyber team, they will be able to invite and + manage other users within the organization and manage the domain list. + + + + + + + + {/* 2 */} + + + Managing Your Domains: + + + + + + Each organization’s domain list should include every internet-facing service. It is the + responsibility of organization admins to manage the current list and identify new domains to add. + + + + + + + Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted + from the DNS returning an error code of NXDOMAIN (domain name does not exist); or 2) if you have + identified that they do not belong to your organization. + + + + + + + If a domain is no longer in use but still exists on the DNS, it is still vulnerable to email + spoofing attacks, where an attacker can send an email that appears to be coming from your + domain. + + + + + + + + {/* 3 */} + + + Understanding Scan Metrics: + + + + + The summary cards show two metrics that Tracker scans: + + + + The percentage of web-hosting services that strongly enforce HTTPS + + + + The percentage of internet-facing services that have a DMARC policy of at least p=”none” + + + + + + + + These metrics are an important first step in securing your services and should be treated as minimum + requirements. Further metrics are available in your organization's domain list. + + + + + Tracker results refresh every 24 hours. + + + + {/* 4 */} + + Develop a prioritized schedule to address any failings: + + + Consider prioritizing websites and web services that exchange Protected data. + + + Where necessary adjust IT Plans and budget estimates where work is expected. + + + + It is recommended that Shared Service Canada (SSC) partners contact their SSC Service Delivery Manager + to discuss action plans and required steps to submit a request for change. + + + + + Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS + Server Certificates for GC public facing web services + + + + + Obtain the configuration guidance for the appropriate endpoints (e.g., web server, network/security + appliances, etc.) and implement recommended configurations. + + + + + {/* 5 */} + + + + To ensure accurate scanning results, configure your firewall and DDoS (Denial of Service) protection + settings to permit required scanning traffic. Work with your IT team and/or your SSC Service Delivery + Manager to add the scanning IP address (52.138.13.28) to your network's allow lists. + + + + + + Our scan requests can be identified by the following User-Agent header:{' '} + + Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0 Tracker-Suivi + (+https://github.com/canada-ca/tracker) + + + + + + {/* 6 */} + + + Links to Review: + + + + Tracker: + + + + Wiki + + + + + List of guidance tags + + + + + + + Web Security: + + + + + + Requirements:{' '} + + Web Sites and Services Management Configuration Requirements + + + + + + + + Implementation:{' '} + + Guidance on securely configuring network protocols (ITSP.40.062) + + + + + + + + + Email Security: + + + + + + Requirements:{' '} + + Email Management Services Configuration Requirements + + + + + + + + Implementation:{' '} + + Implementation guidance: email domain protection (ITSP.40.065 v1.1) + + + + + + + + + + + + + Frequently Asked Questions + + + + + {/* 1 */} + + It is not clear to me why a domain has failed? + + + + Please contact{' '} + + TBS Cyber Security + {' '} + for help. + + + + + {/* 2 */} + + How can I edit my domain list? + + + Admins of an organization can add domains to their list. + + + + Only{' '} + + TBS Cyber Security + {' '} + can remove domains from your organization. Domains are only to be removed from your list when 1) they + no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain + name does not exist); or 2) if you have identified that they do not belong to your organization. + + + + + {/* 3 */} + + Why do other tools show positive results for a domain while Tracker shows negative results? + + + + While other tools are useful to work alongside Tracker, they do not specifically adhere to the + configuration requirements specified in the{' '} + + Email Management Service Configuration Requirements + {' '} + and the{' '} + + Web Site and Service Management Configuration Requirements + + . For a list of allowed protocols, ciphers, and curves review the{' '} + + ITSP.40.062 TLS guidance + + . + + + + + {/* 4 */} + + + What does it mean if a domain is “unreachable”? + + + + + By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside + that set, you need to contact us to let us know. Send an email to{' '} + + TBS Cyber Security + {' '} + to confirm your ownership of that domain. + + + + Another possibility is that your domain is not internet facing. + + + + {/* 5 */} + + Where can I get a GC-approved TLS certificate? + + + + Options include contacting the{' '} + + SSC WebSSL services team + {' '} + and/or using{' '} + + Let's Encrypt + + . For more information, please refer to the guidance on{' '} + + Recommendations for TLS Server Certificates + + . + + + + + {/* 6 */} + + Why does the guidance page not show the domain’s DKIM selectors even though they exist? + + + + Tracker automatically adds DKIM selectors using DMARC reports. Selectors will be added to Tracker when + 1) the domain has a DMARC RUA record which includes "mailto:dmarc@cyber.gc.ca"; and 2) the selector + has been used to sign an email and passed DKIM validation. If your DKIM selectors or any DMARC + information is missing, please email{' '} + + TBS Cyber Security + + . + + + + + The process of detecting DKIM selectors is not immediate. It may take more than 24 hours for the + selectors to appear in Tracker after the conditions are met. + + + + + {/* 7 */} + + + My domain does not send emails, how can I get my domain's DMARC, DKIM, and SPF compliance checks to pass? + + + + + Follow the guidance found in section B.4 of the{' '} + + ITSP.40.065 v1.1 + + . + + + + + {/* 8 */} + + References: + + + + Domain Name System (DNS) Services Management Configuration Requirements - Canada.ca + + + + + Email Management Services Configuration Requirements - Canada.ca + + + + + + Implementation guidance: email domain protection (ITSP.40.065 v1.1) - Canadian Centre for Cyber + Security + + + + + + Mozilla SSL Configuration Generator + + + + + Protect domains that do not send email - GOV.UK (www.gov.uk) + + + + + + + + Service Level Agreement + + + + + + Help Desk: All enquiries submitted via generic mailboxes{' '} + + tracker@tbs-sct.gc.ca + {' '} + will be responded to within 10 business days. + + + + + Application Availability: This cloud-based web application is to be 95% fully operational from 8:00 + AM to 4:00 PM Eastern time on regular business days. + + + + + + + For any questions or concerns, please contact{' '} + + TBS Cyber Security + {' '} + . + + + + ) +} diff --git a/frontend/src/app/RequestScanNotificationHandler.js b/frontend/src/app/RequestScanNotificationHandler.js index 481c7851cb..736d2baa13 100644 --- a/frontend/src/app/RequestScanNotificationHandler.js +++ b/frontend/src/app/RequestScanNotificationHandler.js @@ -1,7 +1,7 @@ import React from 'react' import { useSubscription } from '@apollo/client' import { Box, useToast } from '@chakra-ui/react' -import { t } from '@lingui/macro' +import { t } from "@lingui/core/macro" import { node } from 'prop-types' import { useUserVar } from '../utilities/userState' @@ -69,8 +69,8 @@ export function RequestScanNotificationHandler({ children, ...props }) { data: { getOneTimeScans: subscriptionData.data.sslScanData }, }) toast({ - title: t`SSL Scan Complete`, - description: t`SSL scan for domain "${subscriptionData.data.sslScanData.domain.domain}" has completed.`, + title: t`TLS Scan Complete`, + description: t`TLS scan for domain "${subscriptionData.data.sslScanData.domain.domain}" has completed.`, status: 'info', duration: 9000, isClosable: true, diff --git a/frontend/src/app/ScrollToAnchor.js b/frontend/src/app/ScrollToAnchor.js new file mode 100644 index 0000000000..ab7e3051cb --- /dev/null +++ b/frontend/src/app/ScrollToAnchor.js @@ -0,0 +1,109 @@ +import { useLocation } from 'react-router-dom' +import { useEffect, useRef, useState } from 'react' + +function isInteractiveElement(element) { + const formTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'] + const linkTags = ['A', 'AREA'] + return ( + (formTags.includes(element.tagName) && !element.hasAttribute('disabled')) || + (linkTags.includes(element.tagName) && element.hasAttribute('href')) + ) +} + +function scrollToElement(element) { + const originalTabIndex = element.getAttribute('tabindex') + if ([null, 'none'].includes(originalTabIndex) && !isInteractiveElement(element)) { + element.setAttribute('tabindex', -1) + } + + const originalOutline = getComputedStyle(element).outline + const originalBoxShadow = getComputedStyle(element).boxShadow + if (!isInteractiveElement(element)) { + element.style.boxShadow = 'var(--chakra-shadows-outline)' + // set focus outline to none if the element is not interactive to + // disable the default focus outline (which only appears on some events) + element.style.outline = 'none' + } + + element.addEventListener('blur', () => { + if (!isInteractiveElement(element)) { + if (originalTabIndex === null) element.removeAttribute('tabindex') + element.style.boxShadow = originalBoxShadow + element.style.outline = originalOutline + } + }) + element.focus({ preventScroll: true }) + + // scroll inside setTimeout to prevent scrollIntoView from being ignored + // observed in Brave browser where focus's preventScroll prevents scrollIntoView + setTimeout(() => { + const preferReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + element.scrollIntoView({ behavior: preferReducedMotion ? 'auto' : 'smooth', block: 'start' }) + }, 1) + + return true +} + +export function ScrollToAnchor() { + const location = useLocation() + const lastHash = useRef('') + const [observer, setObserver] = useState(null) + const timer = useRef(null) + + // listen to location change using useEffect with location as dependency + // https://jasonwatmore.com/react-router-v6-listen-to-location-route-change-without-history-listen + useEffect(() => { + if (observer !== null) { + observer.disconnect() + setObserver(null) + } + + if (location.hash) { + lastHash.current = location.hash.slice(1) + } else { + lastHash.current = '' + } + + if (lastHash.current) { + const el = document.getElementById(lastHash.current) + + if (el) { + scrollToElement(el) + } else { + const observer = new MutationObserver((_mutationsList, observer) => { + const el = document.getElementById(lastHash.current) + if (el) { + clearTimeout(timer.current) + timer.current = null + observer.disconnect() + setObserver(null) + scrollToElement(el) + } + }) + + setObserver(observer) + observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }) + + timer.current = setTimeout(() => { + if (observer !== null) { + observer.disconnect() + setObserver(null) + } + }, 5000) + } + } + + return () => { + if (observer !== null) { + observer.disconnect() + setObserver(null) + } + } + }, [location]) + + return null +} diff --git a/frontend/src/app/SlideMessage.js b/frontend/src/app/SlideMessage.js new file mode 100644 index 0000000000..36de7aa7f5 --- /dev/null +++ b/frontend/src/app/SlideMessage.js @@ -0,0 +1,131 @@ +import React from 'react' + +import { useLingui } from '@lingui/react' +import { Trans } from "@lingui/react/macro" +import sigEn from '../images/goc-header-logo-en.svg' +import sigFr from '../images/goc-header-logo-fr.svg' + +import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons' +import { + IconButton, + Box, + Text, + Image, + Link, + Divider, + Flex, + Slide, +} from '@chakra-ui/react' + +import { Link as RouteLink } from 'react-router-dom' +import { bool, func } from 'prop-types' + +const emailUrlEn = 'https://www.tbs-sct.gc.ca/pol/doc-eng.aspx?id=27600' +const itpinUrlEn = + 'https://www.canada.ca/en/government/system/digital-government/modern-emerging-technologies/policy-implementation-notices/implementing-https-secure-web-connections-itpin.html' +const emailUrlFr = 'https://www.tbs-sct.gc.ca/pol/doc-fra.aspx?id=27600' +const itpinUrlFr = + 'https://www.canada.ca/fr/gouvernement/systeme/gouvernement-numerique/technologiques-modernes-nouveaux/avis-mise-oeuvre-politique/mise-oeuvre-https-connexions-web-securisees-ampti.html' + +export function SlideMessage({ isOpen, onToggle, ...props }) { + const { i18n } = useLingui() + return ( + + + + + {'Symbol + + + + Track Digital Security + + + + + Canadians rely on the Government of Canada to provide secure + digital services. The Policy on Service and Digital guides + government online services to adopt good security practices for + practices outlined in the{' '} + + email + {' '} + and{' '} + + web + {' '} + services. Track how government sites are becoming more secure. + + + + + + Privacy + + + + Terms & conditions + + + + Report an Issue + + + + Contact Us + + + + + : } + isRound + onClick={onToggle} + color="white" + bgColor="black" + borderWidth="2px" + borderColor="white" + left={isOpen ? '25%' : 0} + top="50%" + position="sticky" + _hover={{ color: 'accent', borderColor: 'accent', bgColor: 'black' }} + /> + + ) +} + +SlideMessage.propTypes = { + isOpen: bool, + onToggle: func, +} diff --git a/frontend/src/app/TopBanner.js b/frontend/src/app/TopBanner.js index b7d4385f0c..2df0ea448b 100644 --- a/frontend/src/app/TopBanner.js +++ b/frontend/src/app/TopBanner.js @@ -1,28 +1,36 @@ import React from 'react' -import { useLingui } from '@lingui/react' -import { t, Trans } from '@lingui/macro' -import { Box, Button, Flex, Image, useToast } from '@chakra-ui/react' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" +import { Box, Button, Flex, useToast, Image, Link, Skeleton } from '@chakra-ui/react' import { Link as RouteLink } from 'react-router-dom' import { useMutation } from '@apollo/client' +import sigEn from '../images/goc-header-logo-dark-en.svg' +import sigFr from '../images/goc-header-logo-dark-fr.svg' +import trackerLogo from '../images/tracker_v-03-transparent 2.svg' +import trackerText from '../images/trackerlogo 1.svg' + import { LocaleSwitcher } from './LocaleSwitcher' import { Layout } from '../components/Layout' import { useUserVar } from '../utilities/userState' -import sigEn from '../images/goc-header-logo-en.svg' -import sigFr from '../images/goc-header-logo-fr.svg' import { SIGN_OUT } from '../graphql/mutations' +import { PhaseBanner } from './PhaseBanner' +import { useLingui } from '@lingui/react' +import { ABTestWrapper, ABTestVariant } from './ABTestWrapper' +import { bool } from 'prop-types' +import { TourButton } from '../userOnboarding/components/TourButton' -export const TopBanner = (props) => { - const { i18n } = useLingui() +export const TopBanner = ({ initialLoading, ...props }) => { const { isLoggedIn, logout } = useUserVar() const toast = useToast() + const { i18n } = useLingui() const [signOut] = useMutation(SIGN_OUT, { onError(error) { toast({ title: error.message, - description: t`An error occured when you attempted to sign out`, + description: t`An error occurred when you attempted to sign out`, status: 'error', duration: 9000, isClosable: true, @@ -43,61 +51,112 @@ export const TopBanner = (props) => { }) return ( - - - - {'Symbol - + + + + + + {t`Symbol + + + - + + + + {t`Tracker + + + {t`Tracker + + + - {isLoggedIn() ? ( - - ) : ( - <> - - - - )} + + + BETA + + + PREVIEW + + + } + ml="8" + mr="2" + flexShrink="0" + > + This is a new service, we are constantly improving. + - - - + + + + {initialLoading ? ( + <> + + + + + + + + ) : isLoggedIn() ? ( + <> + + + ) : ( + <> + + {window.env?.APP_IS_PRODUCTION === true && ( + + )} + + )} + ) } + +TopBanner.propTypes = { + initialLoading: bool, +} diff --git a/frontend/src/app/VerifyAccountNotificationBar.js b/frontend/src/app/VerifyAccountNotificationBar.js deleted file mode 100644 index 98744c6ee9..0000000000 --- a/frontend/src/app/VerifyAccountNotificationBar.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import { Flex, Text, Box, Link } from '@chakra-ui/react' -import { Link as RouteLink } from 'react-router-dom' -import { Trans } from '@lingui/macro' - -export function VerifyAccountNotificationBar() { - return ( - - - - - To enable full app functionality and maximize your account's - security,{' '} - - please verify your account - - . - - - - - ) -} diff --git a/frontend/src/app/__tests__/App.test.js b/frontend/src/app/__tests__/App.test.js index 3c3cfe34d8..def4e881d6 100644 --- a/frontend/src/app/__tests__/App.test.js +++ b/frontend/src/app/__tests__/App.test.js @@ -4,24 +4,13 @@ import { MemoryRouter } from 'react-router-dom' import { cleanup, render, waitFor } from '@testing-library/react' import { MockedProvider } from '@apollo/client/testing' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { makeVar } from '@apollo/client' -import { en } from 'make-plural/plurals' - import { App } from '../App' - import { UserVarProvider } from '../../utilities/userState' import { REFRESH_TOKENS } from '../../graphql/mutations' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) +import { IS_LOGIN_REQUIRED } from '../../graphql/queries' +import { TourProvider } from '../../userOnboarding/contexts/TourContext' const mocks = [ { @@ -41,6 +30,16 @@ const mocks = [ }, }, }, + { + request: { + query: IS_LOGIN_REQUIRED, + }, + result: { + data: { + loginRequired: false, + }, + }, + }, ] describe('', () => { @@ -49,7 +48,7 @@ describe('', () => { describe('routes', () => { describe('/', () => { it('renders the main page', async () => { - const { getByText } = render( + const { getAllByText } = render( ', () => { - + + + , ) - await waitFor(() => expect(getByText(/Track digital security/i))) + await waitFor(() => expect(getAllByText(/Track digital security/i))) }) }) @@ -86,16 +87,16 @@ describe('', () => { - + + +
, ) - const domains = await waitFor(() => - getByText(/Sign in with your username and password./i), - ) + const domains = await waitFor(() => getByText(/Login to your account/i)) await waitFor(() => { expect(domains).toBeInTheDocument() }) @@ -117,7 +118,9 @@ describe('', () => { - + + + @@ -126,15 +129,13 @@ describe('', () => { ) expect( - queryByText( - /To enable full app functionality and maximize your account's security/, - ), + queryByText(/To enable full app functionality and maximize your account's security/), ).not.toBeInTheDocument() }) }) describe('user is logged in', () => { - it('renders when user not verified', () => { + it('renders when user not verified', async () => { const { getByText } = render( ', () => { - + + + , ) - expect( - getByText( - /To enable full app functionality and maximize your account's security/, - ), - ).toBeInTheDocument() + await waitFor(() => + expect(getByText(/To enable full app functionality and maximize your account's security/)).toBeInTheDocument(), + ) }) it('does not render if user is verified', () => { const { queryByText } = render( @@ -173,7 +174,9 @@ describe('', () => { - + + + @@ -181,10 +184,35 @@ describe('', () => {
, ) expect( - queryByText( - /To enable full app functionality and maximize your account's security/, - ), + queryByText(/To enable full app functionality and maximize your account's security/), ).not.toBeInTheDocument() }) }) + + describe('When login is not required', () => { + it('displays additional navbar options', async () => { + const { queryByRole } = render( + + + + + + + + + + + + + , + ) + await waitFor(() => expect(queryByRole('link', { name: 'Organizations', hidden: false })).toBeInTheDocument()) + }) + }) }) diff --git a/frontend/src/app/__tests__/ContactUsPage.test.js b/frontend/src/app/__tests__/ContactUsPage.test.js new file mode 100644 index 0000000000..76ff46c917 --- /dev/null +++ b/frontend/src/app/__tests__/ContactUsPage.test.js @@ -0,0 +1,64 @@ +import React from 'react' +import { theme, ChakraProvider } from '@chakra-ui/react' +import { MemoryRouter } from 'react-router-dom' +import { cleanup, fireEvent, render, waitFor } from '@testing-library/react' +import { MockedProvider } from '@apollo/client/testing' +import { I18nProvider } from '@lingui/react' +import { i18n } from '@lingui/core' +import { makeVar } from '@apollo/client' +import ContactUsPage from '../ContactUsPage' +import { UserVarProvider } from '../../utilities/userState' + +describe('', () => { + afterEach(cleanup) + + it('renders the page', async () => { + const { getByText } = render( + + + + + + + + + + + , + ) + await waitFor(() => expect(getByText(/Contact the Tracker Team/i))) + }) + + it('Contact Us button can be pressed', async () => { + const { getByRole } = render( + + + + + + + + + + + , + ) + const button = await waitFor(() => getByRole('link', { name: /Contact Us/i })) + await waitFor(() => { + expect(button) + fireEvent.click(button) + }) + }) +}) diff --git a/frontend/src/app/__tests__/FloatingMenu.test.js b/frontend/src/app/__tests__/FloatingMenu.test.js index 4c85f46b1f..2339d3eaa5 100644 --- a/frontend/src/app/__tests__/FloatingMenu.test.js +++ b/frontend/src/app/__tests__/FloatingMenu.test.js @@ -1,34 +1,21 @@ import React from 'react' -import { render, waitFor } from '@testing-library/react' +import { cleanup, render, waitFor } from '@testing-library/react' import { theme, ChakraProvider } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' -import { MemoryRouter, Route } from 'react-router-dom' +import { i18n } from '@lingui/core' +import { createMemoryRouter, MemoryRouter, RouterProvider } from 'react-router-dom' import { fireEvent } from '@testing-library/dom' import { MockedProvider } from '@apollo/client/testing' import { makeVar } from '@apollo/client' - import { FloatingMenu } from '../FloatingMenu' - import { UserVarProvider } from '../../utilities/userState' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: {}, - }, -}) +import { SIGN_OUT } from '../../graphql/mutations' describe('', () => { it('renders', async () => { const { getByText } = render( - + @@ -72,7 +59,7 @@ describe('', () => { }) describe('when the menu is open', () => { describe("and the 'Close' button is clicked", () => { - it('closes the menu', async () => { + it.skip('closes the menu', async () => { const { getByText, queryByText } = render( ', () => { }) }) }) - }) - - describe('when the menu is open', () => { describe("and the 'Sign In' button is clicked", () => { it('redirects to the sign in page', async () => { - let wLocation + const router = createMemoryRouter( + [ + { + path: '/sign-in', + element:
Sign in
, + }, + { + path: '/', + element: , + }, + ], + { + initialEntries: ['/'], + initialIndex: 0, + }, + ) const { getByText } = render( - - - - - - { - wLocation = location - return null - }} - /> - - - + + + + + + , ) - const menuButton = getByText(/Menu/i) - fireEvent.click(menuButton) + + await waitFor(() => { + const menuButton = getByText(/Menu/i) + fireEvent.click(menuButton) + }) await waitFor(() => { expect(getByText(/Sign In/i)).toBeInTheDocument() @@ -152,7 +139,108 @@ describe('', () => { fireEvent.click(signInButton) await waitFor(() => { - expect(wLocation.pathname).toBe('/sign-in') + expect(router.state.location.pathname).toBe('/sign-in') + }) + }) + }) + describe('when user is logged in', () => { + describe('when the Sign Out button is clicked', () => { + afterEach(cleanup) + it('succeeds', async () => { + const mocks = [ + { + request: { + query: SIGN_OUT, + }, + result: { + data: { + signOut: { + status: 'wfewgwgew', + }, + }, + }, + }, + ] + + const { queryByText, getByText } = render( + + + + + + + + + + + , + ) + const menuButton = getByText(/Menu/i) + fireEvent.click(menuButton) + + await waitFor(() => { + expect(queryByText(/Sign Out/i)).toBeInTheDocument() + }) + + const signOutButton = getByText(/Sign Out/i) + fireEvent.click(signOutButton) + + await waitFor(() => { + expect(queryByText(/You have successfully been signed out./i)) + }) + }) + it('fails', async () => { + const mocks = [ + { + request: { + query: SIGN_OUT, + }, + result: { + error: { + errors: [{ message: 'foobar' }], + }, + }, + }, + ] + + const { queryByText, getByText } = render( + + + + + + + + + + + , + ) + const menuButton = getByText(/Menu/i) + fireEvent.click(menuButton) + + await waitFor(() => { + expect(queryByText('Sign Out')).toBeInTheDocument() + }) + + const signOutButton = getByText('Sign Out') + fireEvent.click(signOutButton) + + await waitFor(() => { + expect(queryByText(/An error occured when you attempted to sign out/i)) + }) }) }) }) diff --git a/frontend/src/app/__tests__/FloatingMenuLink.test.js b/frontend/src/app/__tests__/FloatingMenuLink.test.js index 7a6d8f3f16..e074f34bee 100644 --- a/frontend/src/app/__tests__/FloatingMenuLink.test.js +++ b/frontend/src/app/__tests__/FloatingMenuLink.test.js @@ -1,34 +1,19 @@ import React from 'react' -import { render, waitFor } from '@testing-library/react' +import { render, waitFor, fireEvent } from '@testing-library/react' import { theme, ChakraProvider } from '@chakra-ui/react' import { I18nProvider } from '@lingui/react' -import { setupI18n } from '@lingui/core' -import { MemoryRouter, Route } from 'react-router-dom' -import { fireEvent } from '@testing-library/dom' +import { i18n } from '@lingui/core' +import { createMemoryRouter, MemoryRouter, RouterProvider } from 'react-router-dom' import { MockedProvider } from '@apollo/client/testing' import { makeVar } from '@apollo/client' - import { FloatingMenuLink } from '../FloatingMenuLink' - import { UserVarProvider } from '../../utilities/userState' -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: {}, - }, -}) - describe('', () => { it('renders', async () => { const { getByText } = render( - + @@ -44,31 +29,31 @@ describe('', () => { describe('when the link is clicked', () => { it('redirects', async () => { - let wLocation + const router = createMemoryRouter( + [ + { + path: '/sign-in', + element:
Sign in page
, + }, + { + path: '/', + element: , + }, + ], + { + initialEntries: ['/'], + initialIndex: 0, + }, + ) const { getByText } = render( - - - - - - { - wLocation = location - return null - }} - /> - - - + + + + + + , ) @@ -76,7 +61,7 @@ describe('', () => { fireEvent.click(signInLink) await waitFor(() => { - expect(wLocation.pathname).toBe('/sign-in') + expect(router.state.location.pathname).toBe('/sign-in') }) }) }) diff --git a/frontend/src/app/__tests__/Footer.test.js b/frontend/src/app/__tests__/Footer.test.js index 84f13808b7..435ebc568e 100644 --- a/frontend/src/app/__tests__/Footer.test.js +++ b/frontend/src/app/__tests__/Footer.test.js @@ -2,20 +2,9 @@ import React from 'react' import { I18nProvider } from '@lingui/react' import { theme, ChakraProvider } from '@chakra-ui/react' import { render } from '@testing-library/react' -import { setupI18n } from '@lingui/core' - +import { i18n } from '@lingui/core' import { Footer } from '../Footer' -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: {}, - }, -}) - describe('